-- UIMainMap
-- Create by zouyb
-- 主界面地图

require "game/ui/module/Avatar"
require "game/ui/form/main/UINewDungeonEffect"
require "game/ui/form/hero/UIHeroGainEffect"
require "game/ui/form/main/UISubAreaList"
require "game/ui/form/combat_stat/UICombatStat"
require "game/ui/base/util/MainMapEffects"
require "game/ui/form/dungeon/UIDungeonRevive"
require "game/ui/form/shop/UIBlackMarket"
require "game/ui/form/eggs/UIEggs"
require "game/ui/form/shop/UIPetShop"
require "game/ui/form/hero/UIHeroSpeak"
require "game/ui/form/daily_task/UIDailyTask"
require "game/ui/form/mail/UIMailList"
require "game/ui/form/achievement/UIDpExplain"
require "game/ui/form/achievement/UIGameAchiv"
require "game/ui/form/reminder/UIMsgReminder"
require "game/ui/form/sky_city/UISkyCity"
require "game/ui/form/shop/UIMercenaryCamp"
require "game/ui/module/AutoOperationM"
require "game/ui/form/awake/UIAwakeMain"

UIMainMap = class("UIMainMap", function()
    return cc.Layer:create();
end);

function UIMainMap.create()
    return UIMainMap.new();
end

-- 需要暂离置灰的节点
local blendNodes = {};

-- 玩家操作检测时间
local USER_IDLE_LIMIT_TIME = 600;    -- 允许10分钟无操作时间
local WEB_BOARD_CHECK = 3600;        -- WEB公告拉取比较检查时间

-- 充值未到账的时间
local CHARGE_OUT_TIME = 15;

-- PT充值的ID
local PT_RECHARGE_ID = 10101;

-- 内部函数声明
local whenDoCompound;
local showDpInfo;
local resize;

--如果该部位尚未佩戴道具，则自动装上。
function autoEquip(classId)
    if ItemM.query(classId, "type") ~= ITEM_TYPE_WEAPON and ItemM.query(classId, "type") ~= ITEM_TYPE_ARTIFACT then
        return;
    end
    local type = EquipM.query(classId, "type");
    if type == EQUIP_TYPE_MEDAL or type == EQUIP_TYPE_WEAPON then
        return ;
    end

    if not EquipM.getEquip(type, 1) then
        DungeonActionM.go("do_equip", 0, classId);
    end
end

-- 构造函数
function UIMainMap:ctor()
    self:setName("UIMainMap");

    local node = cc.CSLoader:createNode("layout/main/MainMap.csb");
    self.node = node;
    self:addChild(node);
    self.eggParentNode = findChildByName(self.node, "map/egg_parent_node");

    self:setLocalZOrder(UIMgr.MAIN_MAP_ZORDER);

    self.oldZOrder = self:getLocalZOrder();

    -- 初始化地图和关卡图标
    self:initMap();
    self:refreshIcons();

    -- 初始化需要暂离置灰的节点
    self:initBlendNodes();

    -- 操作定时检测累计时间初始化
    self:checkUserAction();

    self.workShopLeftTime = {["min"] = 0, ["sec"] = 0};

    -- 注册点击事件
    self:registerTouchEvent();

    -- 注册事件回调处理函数
    self:registerEventCallback();

    -- 注册返回键处理函数
    self:registerBackKeyEventHandler();

    -- 定时检查炼金产出
    self:checkAlchemyProduction();

    -- 显示消息提醒UI
    self:updateMsgReminderUI();

    -- 播放特效
    if AlchemyWorkshopM.isAllGuideFinished() then
        local function delay()
            self:playEffect();
        end
        performWithDelay(self, delay, 2);
    end

    -- 适配
    resize(node);

    -- 在迷宫内限制操作
    if DungeonM.isInDungeon() then
        self:checkInDungeonState();
    end
end

-- 注册点击事件
function UIMainMap:registerTouchEvent()
    -- 黑市商人
    self.blackMarket = findChildByName(self.node, "map/black_market");
    self.blackMarket:addTouchEventListener(function(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            local uiForm = UIBlackMarket.create(sender:getTag());
            UIMgr.getCurrentScene():addForm(uiForm);
            AudioM.playFx("market_open");
        end
    end);

    -- 佣兵营地
    local petShopIcon = findChildByName(self.node, "map/pet_shop/pet_shop_icon/icon");
    self.petShop = findChildByName(self.node, "map/pet_shop");
    local function onClickPetShop(sender, eventType)
        if FeatureItemM.isFeatureOpen("scaffold") ~= false then
            local function doEnterMecenaryCamp()
                local uiForm = UIMercenaryCamp.create();
                UIMgr.getCurrentScene():addForm(uiForm);
                AudioM.playFx("ui_open1");
            end

            -- 检查是否下载独立资源包
            checkExtraPackDownload(doEnterMecenaryCamp);
        end
    end
    self:registerEnterClick(self.petShop, petShopIcon, onClickPetShop);
    self:registerEnterClick(petShopIcon, petShopIcon, onClickPetShop);

    -- 马戏团
    local circusIcon = findChildByName(self.node, "map/circus/circus_icon/icon");
    self.circus = findChildByName(self.node, "map/circus");
    local function onClickCircus(sender, eventType)
        if FeatureItemM.isFeatureOpen("tick") ~= false then
            local function doOpenDailyTask()
                local uiForm = UIDailyTask.create();
                UIMgr.getCurrentScene():addForm(uiForm);
                AudioM.playFx("ui_open1");
            end
            -- 检查是否下载独立资源包
            checkExtraPackDownload(doOpenDailyTask);
        end
    end
    self:registerEnterClick(self.circus, circusIcon, onClickCircus);
    self:registerEnterClick(circusIcon, circusIcon, onClickCircus);

    -- 邮箱
    local mailBoxIcon = findChildByName(self.node, "map/mail_box/mail_box_icon/icon");
    self.mailBox = findChildByName(self.node, "map/mail_box");
    local function onClickMailBox(sender, eventType)
        local uiForm = UIMailList.create();
        UIMgr.getCurrentScene():addForm(uiForm);
        AudioM.playFx("ui_open1");
    end
    self:registerEnterClick(self.mailBox, mailBoxIcon, onClickMailBox);
    self:registerEnterClick(mailBoxIcon, mailBoxIcon, onClickMailBox);

    -- 成就
    local achivIcon = findChildByName(self.node, "map/achiv/achiv_icon/icon");
    self.achivPanel = findChildByName(self.node, "map/achiv");
    local function onAchivClick(sender, eventType)
        local uiForm = UIGameAchiv.create();
        UIMgr.getCurrentScene():removeFormByName("UIGameAchiv");
        UIMgr.getCurrentScene():addForm(uiForm);
        AudioM.playFx("ui_open1");
    end
    self:registerEnterClick(self.achivPanel, achivIcon, onAchivClick);
    self:registerEnterClick(achivIcon, achivIcon, onAchivClick);

    -- 飞船
    local airShipIcon = findChildByName(self.node, "map/airship/airship_icon/icon");
    self.airShip = findChildByName(self.node, "map/airship");
    local function onClickAirShip(sender, eventType)
        if ServerStatusM.query(MODULE_SWITCH_AIRSHIP_CLOSE) == 1 then
            -- 弹出天空之城关闭提示框
            confirm2(CONFIRM_TYPE_OK, getLocStr("slime_note"), getLocStr("sky_city_is_close"));
            return;
        end

        if FeatureItemM.isFeatureOpen("key") ~= false and not NewAirShipM.isFinishGuide() then
            local function onAirSip()
                local uiForm = UISkyCity.create();
                UIMgr.getCurrentScene():addForm(uiForm);
                AudioM.playFx("ui_open1");
            end

            -- 检查是否下载独立资源包
            checkExtraPackDownload(onAirSip);
        end
    end
    if ServerStatusM.query(MODULE_SWITCH_IOS_BLOCK_CONTENT) ~= 1 then
        self:registerEnterClick(self.airShip, airShipIcon, onClickAirShip);
        self:registerEnterClick(airShipIcon, airShipIcon, onClickAirShip);
    end

    -- 遗迹
    local mineIcon = findChildByName(self.node, "map/mine/mine_enter/icon");
    self.mine = findChildByName(self.node, "map/mine");
    local function onClickRuins()
        if ServerStatusM.query(MODULE_SWITCH_AIRSHIP_CLOSE) == 1 then
            -- 弹出天空之城关闭提示框
            confirm2(CONFIRM_TYPE_OK, getLocStr("slime_note"), getLocStr("sky_city_is_close"));
            return;
        end

        local function onAirSip()
            if SkyRelicM.isRelicFound() then
                require "game/ui/form/sky_city/UISkyCityRuins";
                UIMgr.getCurrentScene():removeFormByName("UISkyCityRuins");
                -- 打开遗迹界面
                local uiForm = UISkyCityRuins.create();
                UIMgr.getCurrentScene():addForm(uiForm);
            else
                -- 功能还没有开启给出提示
                require "game/ui/form/sky_city/UIRelicFix";
                local uiForm = UIRelicFix.create();
                UIMgr.getCurrentScene():addForm(uiForm);
            end
            AudioM.playFx("ui_open1");
        end

        -- 检查是否下载独立资源包
        checkExtraPackDownload(onAirSip);
    end
    if ServerStatusM.query(MODULE_SWITCH_IOS_BLOCK_CONTENT) ~= 1 then
        self:registerEnterClick(self.mine, mineIcon, onClickRuins);
        self:registerEnterClick(mineIcon, mineIcon, onClickRuins);
    end

    -- 活动
    local activityIcon = findChildByName(self.node, "map/activity/activity_icon");
    local activityImg = findChildByName(self.node, "map/activity/icon");
    self.activityPanel = findChildByName(self.node, "map/activity");
    local function onClickActivity()
        if RotineActivityM.isInRotineActivity() then
            -- 常规活动
            require "game/ui/form/activity/UIRotineActivity"
            UIMgr.getCurrentScene():removeFormByName("UIRotineActivity");
            local uiForm = UIRotineActivity.create();
            UIMgr.getCurrentScene():addForm(uiForm);

            AudioM.playFx("ui_open1");
        else
            -- 活动副本
            -- 活动ID
            local activityId = ActivityDungeonM.getCurrentActivityId();

            if activityId <= 0 then
                -- 重新检查界面
                self:checkActivity();
                return;
            end

            local function onOpenActvity()
                -- 打开活动副本界面
                require "game/ui/form/activity/UIActivityDungeon"
                UIMgr.getCurrentScene():removeFormByName("UIActivityDungeon");
                local uiActivityDungeon = UIActivityDungeon.create(activityId);
                UIMgr.getCurrentScene():addForm(uiActivityDungeon);

                AudioM.playFx("ui_open1");
            end

            -- 下载条件
            local function condition()
                -- 此处为无条件需要下载
                return true;
            end

            checkExtraPackDownload(onOpenActvity, false, condition);
        end
    end
    self:registerEnterClick(self.activityPanel, activityImg, onClickActivity);
    self:registerEnterClick(activityIcon, activityImg, onClickActivity);
    self:registerEnterClick(activityImg, activityImg, onClickActivity);

    -- 冈布奥觉醒
    local awakeIcon = findChildByName(self.node, "map/awake/awake_icon");
    self.awake = findChildByName(self.node, "map/awake");
    local function onClickAwake(sender, eventType)
        if FeatureItemM.isFeatureOpen("awake") ~= false then
            local function doOpenAwake()
                local uiForm = UIAwakeMain.create();
                UIMgr.getCurrentScene():addForm(uiForm);
                AudioM.playFx("ui_open1");
            end

            -- 检查是否下载独立资源包
            checkExtraPackDownload(doOpenAwake);
        end
    end
    self:registerEnterClick(self.awake, awakeIcon, onClickAwake);
    self:registerEnterClick(awakeIcon, awakeIcon, onClickAwake);

    -- 活动入口效果
    self:redrawActivity();

    -- 命运的链
    local fateIcon = findChildByName(self.node, "map/fate/fate_icon/icon");
    self.fatePanel = findChildByName(self.node, "map/fate");
    local function onFateClick(sender, eventType)
        if true ~= LocationM.prepare() then
            return;
        end
        require "game/ui/form/fate_chain/UIFateChain"
        UIMgr.getCurrentScene():removeFormByName("UIFateChain");
        local uiForm = UIFateChain.create();
        UIMgr.getCurrentScene():addForm(uiForm);
        AudioM.playFx("ui_open1");
    end
    self:registerEnterClick(self.fatePanel, fateIcon, onFateClick);
    self:registerEnterClick(fateIcon, fateIcon, onFateClick);
end

-- 活动入口的效果
function UIMainMap:redrawActivity()
    local activityNode = findChildByName(self.node, "map/activity");
    local effectNode = findChildByName(activityNode, "effect");
    local activityIcon = findChildByName(activityNode, "activity_icon");
    local icon = findChildByName(activityNode, "icon");
    local heroIcon = findChildByName(icon, "hero");
    heroIcon:setVisible(true);
    effectNode:removeAllChildren();
    icon:loadTexture("images/ui/main/event.png");

    if RotineActivityM.isInRotineActivity() then
        -- 常规活动
        local rotineType = RotineActivityM.getRotineInfo()["rotine_type"];
        local iconPath = rotineType .. "_enter";
        activityIcon:loadTexture(getRotineIconPath(iconPath));
        iconPath = rotineType .. "_tip";
        icon:loadTexture(getRotineIconPath(iconPath));

        activityIcon:setPosition(cc.p(120, -10));
        icon:setPosition(cc.p(65, -25));

        heroIcon:setVisible(false);
        return;
    else
        -- 活动副本
        activityIcon:setPosition(cc.p(91.5, -5));
        icon:setPosition(cc.p(45, -19));

        -- 活动信息
        local activityId = ActivityDungeonM.getCurrentActivityId();
        if activityId <= 0 or not ActivityDungeonM.isActivityOpen() then
            return;
        end

        local activityInfo = ActivityDungeonM.query(activityId);

        -- 活动的图片
        local iconPath = activityInfo["icon"];
        local offset = activityInfo["offset"] or {0, 0};
        activityIcon:loadTexture(getActivityEnterIconPath(iconPath));

        if not activityIcon.oriPos then
            activityIcon.oriPos = {activityIcon:getPositionX(), activityIcon:getPositionY()};
        end
        local oriPos = activityIcon.oriPos;
        activityIcon:setPosition(cc.p(oriPos[1] + offset[1], oriPos[2] + offset[2]));

        -- 英雄图片
        local heroIcon = findChildByName(activityNode, "icon/hero");
        local heroIconPath = getHeroIconPath(PetM.getDefaultIcon(activityInfo["pet_id"]));
        -- 如果有额外光效
        local effectId = activityInfo["effect"];
        if type(effectId) == "number" then
            playEffect(effectNode, effectId);
        end

        -- 英雄图片
        local heroIconPath = getHeroIconPath(PetM.getDefaultIcon(activityInfo["pet_id"]));
        heroIcon:loadTexture(heroIconPath);
        heroIcon:setScale(0.7);
        heroIcon:setVisible(true);

        -- 如果配置了大地图入口图标，需要替换英雄图标
        local dbase = activityInfo["dbase"];
        if type(dbase["event_icon"]) == "string" and dbase["event_icon"] ~= "" then
            heroIcon:loadTexture(getMainIconPath2(dbase["event_icon"]));
            heroIcon:setScale(1.0);
            heroIcon:setPositionY(45);
        end

        -- 入口图标位移
        local offset2 = activityInfo["offset2"] or {0, 0};
        local enterIcon = findChildByName(activityNode, "icon");
        if not enterIcon.oriPos then
            enterIcon.oriPos = {enterIcon:getPositionX(), enterIcon:getPositionY()};
        end
        oriPos = enterIcon.oriPos;
        enterIcon:setPosition(cc.p(oriPos[1] + offset2[1], oriPos[2] + offset2[2]));
    end
end

-- 入口点击回调函数
function UIMainMap:registerEnterClick(node, icon, callback)
    local function onClickFunc(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 1.变小
            local downTime  = 0.25/2;
            local scaleDown = cc.ScaleTo:create(downTime, 0.8);
            icon:runAction(scaleDown);
        elseif eventType == ccui.TouchEventType.ended then
            -- 2.变大
            local downTime  = 0.25/2;
            local scaleUp = cc.ScaleTo:create(downTime, 1);
            icon:runAction(scaleUp);
        elseif eventType == ccui.TouchEventType.canceled then
            -- 3.变大
            local downTime  = 0.25/2;
            local scaleUp = cc.ScaleTo:create(downTime, 1);
            icon:runAction(scaleUp);
        end

        if eventType == ccui.TouchEventType.ended then
            if type(callback) == "function" then
                callback();
            end
        end
    end
    node:addTouchEventListener(onClickFunc);
end

-- 注册事件回调处理函数
function UIMainMap:registerEventCallback()
    -- 关注消息以重绘
    ME.user.dbase:registerCb("UIMainMap", { "dungeon_progress", }, function()
        self:refreshIcons();
    end);

    -- 关注登录“在地牢中”消息
    EventMgr.register("UIMainMap", event.MESSAGE_DONE, function(para)
        -- 保存信息，等炼金提示完毕后再处理
        self.messageDonePara = para;
        self.messageDoneFlag = true;
        self:handleMessageDone();
    end);

    -- 强制关闭客户端
    EventMgr.register("UIMainMap", event.FORCE_SHUTDOWN, function(para)
        if tonumber(self.shutdown_flag) + 30 > os.time() then
            return;
        end

        self.shutdown_flag = os.time();
        confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("attrib_error_title"), para.reason or "", exitApp, exitApp);

        -- 定时关闭socket
        performWithDelay(self, function() Socket.disconnect(); end, 7);
    end);

    -- dialog_ok
    EventMgr.register("UIMainMap", event.DIALOG_OK, function(msg)
        alert(getDialogStr(msg));
    end);

    EventMgr.register("UIMainMap", event.START_DUNGEON, function(layer)
        -- 圣光印记
        local arr = PropM.combine(ME.user, "light_imprint", 49);
        if UIDungeonMgr:getCurLevel() == nil and arr[3] > 0 then
            -- 触发特技发动事件
            ME.user.dbase:setTemp("speicial_skill_trigger", arr[1]);
        end

        -- 关闭界面
        self:removeForms();
        if UIMgr:getCurrentScene():isOpen("UIEnterDungeon") then
            UIMgr:getCurrentScene():removeFormByName("UIEnterDungeon");
        end
        UIMgr.getCurrentScene():removeFormByName("UISubAreaList");

        local dungeonData = DungeonM.getCurrentDungeon();
        local layer = DungeonM.currentLayer();
        UIDungeonMgr.startDungeon(dungeonData, layer);
    end);

    EventMgr.register("UIMainMap", event.DUNGEON_AREADY, function(args)
        -- 关闭界面
        -- 如果有携带神器 则自动穿上神器
        local artifactId = ME.user:getCarryArtifact();
        if artifactId ~= 0 and args.restart == 1 then
            autoEquip(artifactId);
        end
    end);

    -- 传承任务
    EventMgr.register("UIMainMap", event.FINISH_INHERIT_TASK, function(args)
        local activePet = ME.user.dbase:query("active_pet", 0);
        local taskId = args.taskId;
        local function showDialog()
            -- 提示获得认可
            local extraData = { ["sub_name"] = DailyTaskM.query(taskId, "name"), ["frame_icon"] = DailyTaskM.query(taskId, "icon"), ["cancel_callback"] = nil};
            local uiForm = UIFeatureDesc.create(500, nil, nil, extraData);
            UIMgr.getCurrentScene():addForm(uiForm);
        end

        local msgList = {string.format(getLocStr("daily_task_inherit_text"), DailyTaskM.query(taskId, "name")), };
        local UIHeroSpeak = UIHeroSpeak.create(self, activePet, msgList, "B_FLY_IN", "B_FLY_OUT", nil, showDialog);
        AudioM.playFx("slime_swordman_app");
        UIMgr.getCurrentScene():addForm(UIHeroSpeak);
    end);

    EventMgr.register("UIMainMap", event.CONTINUE_DUNGEON, function(param)
        -- 关闭界面
        self:removeForms();
        UIMgr.getCurrentScene():removeFormByName("UISubAreaList");

        local dungeonData = DungeonM.getCurrentDungeon();
        local layer = DungeonM.currentLayer();
        UIDungeonMgr.startDungeon(dungeonData, layer);
    end);

    -- 解锁区域回调处理
    EventMgr.register("UIMainMap", event.UNLOCK_DUNGEON_AREA, function(param)
        local areaId = param["id"];

        self:refreshIcons();

        -- 播放区域激活动画
        self:playUnlockAreaAnimation(areaId);
    end);

    -- 触发英雄对话
    local function playHeroSpeak()
        if not self.heroSpeakQueue or #self.heroSpeakQueue == 0 then
            return;
        end
        local args = self.heroSpeakQueue[1];
        table.remove(self.heroSpeakQueue, 1);
        local heroId = args.heroId;
        local msgList = args.msgList;
        local endEffect = args.endEffect;
        local uiForm = UIHeroSpeak.create(self, heroId, msgList, "B_FLY_IN", "B_FLY_OUT", nil, endEffect, args);
        UIMgr.getCurrentScene():addForm(uiForm);
    end

    -- 注册冈布奥对话事件
    EventMgr.register("UIMainMap", event.SLIME_TALK, function(args)
        local heroId = args.heroId;

        -- 只针对创世神做新机制处理
        if heroId == 39 then
            if not self.HeroSpeakQueue then
                self.heroSpeakQueue = {};
                -- 可视情况添加，最好是主要界面，如UIDungoenMain,UIHeroMain等
                self.formListTriggerWhenFocus = {"UIMarketMain", "UIMainMap"}
            end

            table.insert(self.heroSpeakQueue, args);

            if not UIMgr.getCurrentScene():isOpen("UIHeroSpeak") then
                -- 立即触发
                playHeroSpeak();
            else
                -- 检查下待触发的界面是否至少有一项是打开着，否则提示Error
                local isOpen = false;
                if self.formListTriggerWhenFocus then
                    for _, name in pairs(self.formListTriggerWhenFocus) do
                        if UIMgr.getCurrentScene():isOpen(name) then
                            isOpen = true;
                            break;
                        end
                    end
                end

                if not isOpen then
                    error("SLIME_TALK连续触发，但是没有配置再次触发的界面条件");
                end
            end
            return;
        elseif SlimeEventM.query(args.eventId, "rule") == "spring_mop_up" then
            -- 春节扫荡的，先不显示
            return;
        elseif SlimeEventM.query(args.eventId, "rule") == "spring_exchange" then
            -- 春节兑换的
            local para = args.para or {};
            local id = tonumber(para.exchange_id);
            if table.indexOf({51,52,53,54}, id) >= 0 then
                -- 获得冈布奥奖励的，等到GainEffect界面关闭再出来
                return;
            end
        end

        -- 其他的走老的流程
        local msgList = args.msgList;
        local endEffect = args.endEffect;

        local uiForm = UIHeroSpeak.create(self, heroId, msgList, "B_FLY_IN", "B_FLY_OUT", nil, endEffect, args);
        UIMgr.getCurrentScene():addForm(uiForm);
        return;
    end);

    -- 获得英雄后回调
    EventMgr.register("UIMainMap", event.GAIN_PET, function(classId)
        if UIMgr.getCurrentScene():isOpen("UIHeroGainEffect") then
            UIMgr.getCurrentScene():removeFormByName("UIHeroGainEffect");
        end
        -- 需要先播放对话的
        local slimeList = FormulaM.invoke("NEED_TALK_BEFORE_GAIN_EFFECT");
        if table.indexOf(slimeList, classId) ~= -1 then
            -- 获取对话
            local msgList = FormulaM.invoke("GET_SLIME_TALKS", classId, "gain_slime");
            EventMgr.fire(event.SLIME_TALK, {["heroId"] = classId,
                ["msgList"] = msgList, ["endEffect"] = "gain_pet"});
            AudioM.playFx("slime_pastor_app");
        else
            local uiHeroInfo = UIHeroGainEffect.create(classId);
            UIMgr.getCurrentScene():addForm(uiHeroInfo);
        end

        self:refreshIcons();
    end);

    -- 注册合成成功的事件回调
    EventMgr.register("UIMainMap", event.DO_COMPOUND, function(args)
        whenDoCompound(args);
    end);

    -- 注册玩家死亡的回调
    EventMgr.register("UIMainMap", event.HERO_DIE, function(para)
        local function showReviveForm()
            local trueFloor = DungeonM.currentLayer();
            local uiFloor = nil;
            if UIDungeonMgr.getCurLevel() then
                uiFloor = UIDungeonMgr.getCurLevel():getUICurFloor();
                if DungeonM.getState() == DUNGEON_LOADING or trueFloor and trueFloor ~= uiFloor then
                    -- 正在进入下一层，不显示
                    return;
                end
            end

            -- 显示复活界面
            if UIMgr.getCurrentScene():isOpen("UIDungeonRevive") then
                UIMgr.getCurrentScene():removeFormByName("UIDungeonRevive");
            end

            local uiDungeonRevive = UIDungeonRevive.create();
            UIMgr.getCurrentScene():addForm(uiDungeonRevive);
        end

        local delay = 0;
        if para and para["delay"] then
            delay = para["delay"];
        end

        if para and type(para["hero_talk"]) == "string" and string.len(para["hero_talk"]) > 0 then
            -- 需要先英雄对话
            startDialogueTemplate(para["hero_talk"], showReviveForm);
        else
            performWithDelay(self, showReviveForm, 0.01 + delay);
        end

        GameAnalytics.failLevel("level_"..DungeonM.getDungeonId().."_"..DungeonM.currentLayer());
    end);

    -- 注册获得焦点的回调
    EventMgr.register("UIMainMap", event.FOCUS_CHANGED, function(para)
        if self.formListTriggerWhenFocus then
            if table.indexOf(self.formListTriggerWhenFocus, para["get"]) ~= -1 then
                -- 目标窗口获得焦点，尝试触发遗留英雄讲话事件
                playHeroSpeak();
            end
        end

        if para["get"] == "UIMainMap" then
            -- UIMainMap 获得焦点，播放TopMenu和 BottomMenu飞入屏幕的动画
            self:topAndBottomMenuFlyIn();

            -- 检查佣兵营地
            self:checkPetShop();

            -- 检查马戏团
            self:checkCircus();

            -- 检查飞船
            self:checkAirShip();

            -- 检查遗迹
            self:checkRelic();

            -- 检查冈布奥觉醒
            self:checkAwake();

            -- 检查常规
            self:checkActivity();

            -- 检查命运之链
            self:checkFate();

            -- 恢复动画
            self:resumeEffect();

            -- 检查是否需要其他提醒
            if self.openGameBoard then
                -- 检查炼金提示
                self:confirmWorkshopBonus();
                -- 去除标记
                self.openGameBoard = false;
            end

            if self.remindFeatureEffect == "key" then
                self.remindFeatureEffect = nil;

                -- 滚动地图界面到飞艇部分
                local map = findChildByName(self.node, "map");
                local size = map:getInnerContainerSize();
                local airship = findChildByName(map, "airship");
                local scrollTime = 1;

                local percent = airship:getPositionY() * 100 / size.height;
                if percent < 0 then
                    percent = 0;
                end
                -- 滚动
                map:scrollToPercentVertical(percent, scrollTime, true);

                -- 播放飞艇残骸飞出地面效果
                performWithDelay(self, function ()
                    palyAirShipStarEffect(airship);
                end, scrollTime);
            end
        elseif para["lose"] == "UIMainMap" then
            local bottomMenu = UIMgr.getCurrentScene().bottomMenu;
            bottomMenu:setVisible(false);
        end
    end);

    local function updateBlackMarketPos()
        local shopId = BlackMarketM.getShopId();
        -- 黑市商人消失，或者黑市商人重新刷新
        if shopId == nil or shopId == 0 or (self.lastShopId ~= nil and shopId ~= self.lastShopId) then
            self.blackMarket:setVisible(false);
            self.blackMarket:removeAllChildren();

            if UIMgr.getCurrentScene():isOpen("UIBlackMarket") then
                UIMgr.getCurrentScene():removeFormByName("UIBlackMarket");
                UIMgr.getCurrentScene():removeFormByName("UIBlackMarketGoodsDetail");
                alert(getLocStr("black_market_is_gone"));
            end

            if shopId == nil or shopId == 0 then
                -- 消失直接返回
                return;
            end
        end

        -- 更新商人位置
        self.lastShopId = shopId;
        local pos    = BlackMarketM.queryRule(shopId, "pos");
        local icon   = BlackMarketM.queryRule(shopId, "icon");
        local offset = BlackMarketM.queryRule(shopId, "effectOffset");
        if pos ~= nil then
            self.blackMarket:setTag(shopId);
            self.blackMarket:loadTexture("images/ui/common/" .. icon .. ".png");
            self.blackMarket:setVisible(true);
            self.blackMarket:setPosition(pos.x, pos.y);
            playBlackMarketEffect(self.blackMarket, offset.x, offset.y);
        end
    end

    -- 关注黑市商人信息更新事件
    EventMgr.register("UIMainMap", event.BLACKMARKET_INFO, function()
        updateBlackMarketPos();
    end)

    -- 关注领取成就奖励事件
    -- 注意：不要使用 BONUS_ITEM 事件，否则在迷宫中获得物品也会执行刷新逻辑
    EventMgr.register("UIMainMap", event.TAKE_ACHIV_BONUS, function(args)
        self:refreshAreas();
    end);

    -- 关注地图刷新事件
    EventMgr.register("UIMainMap", event.REFRESH_AREA, function(args)
        self:refreshAreas();
    end);

    EventMgr.register("UIMainMap", event.GAME_ACHIV_BONUS, function(args)
        self:checkAchiv();
    end);

    -- 关注设置成就数据事件
    EventMgr.register("UIMainMap", event.SET_ACHIEVEMENTS, function()
        self:refreshIcons();
    end);

    -- 充值扣款成功事件回调
    EventMgr.register("UIMainMap", event.RECHARGE_OK, function(para)
        -- 如果不是等待的账单
        if not ChargeM.isWaitingRecharge() then
            return;
        end

        -- 抛出一个loading界面
        showCommunicatingForm(getLocStr("waiting"));

        local sku = para.sku;

        local function callback()
            -- 关闭通信中界面
            closeCommunicatingForm();

            -- 如果当前正在等待服务器到账，并且发起过充值，则需要弹未到账提示框（登录时充值补单无需弹框）
            if ChargeM.isWaitingRecharge() and ME.user.dbase:queryTemp("request_charge") == 1 then
                ME.user.dbase:deleteTemp("request_charge");

                -- 如果当前已经有一个充值未到账的提示框，则不再提示
                local currentDialog = UIMgr.getCurrentScene():getCurrentDialog();
                if currentDialog and currentDialog.extraPara and
                   currentDialog.extraPara.waitingRecharge then
                    return;
                end

                if ChargeM.hasTransFinished() then
                    -- 已经完成充值交易，但是服务器延迟到账了
                    confirm2(CONFIRM_TYPE_OK, getLocStr("pay_succ"), getLocStr("recharge_delay"),
                        nil, nil, nil, nil, { ["waitingRecharge"] = true, });
                else
                    -- 充值未到账
                    confirm2(CONFIRM_TYPE_OK, getLocStr("recharge_error_title"), getLocStr("recharge_error"),
                        showFAQs, nil, getLocStr("response_problem"), nil,
                        { ["waitingRecharge"] = true,  });
                end

                -- 如果有订单号，通知服务器记录日志，以便排查
                local orderId = ChargeM.getLastOrderId();
                if orderId then
                    local payTime = math.ceil(TimeM.getServerTime() - CHARGE_OUT_TIME);
                    Operation.cmd_charge_error(sku, payTime, orderId);
                end
            end
        end

        if self.chargeNode == nil then
            -- 创建一个节点专门用于管理充值定时器
            local chargeNode = cc.Node:create();
            self:addChild(chargeNode);
            self.chargeNode = chargeNode;
        end

        self.chargeNode:stopAllActions();
        performWithDelay(self.chargeNode, callback, CHARGE_OUT_TIME);
    end);

    -- 充值结果
    EventMgr.register("UIMainMap", event.RECHARGE_RESULT, function(args)
        if UIMgr:getCurrentScene():isOpen("UICommunicating") then
            closeCommunicatingForm();
        end

        if self.chargeNode then
            trace("UIMainMap", "chargeNode stopAllActions.");
            self.chargeNode:stopAllActions();
        end

        local bonusList     = args.bonus;
        local compensateGem = args.compensateGem or 0;
        local bonus;
        if type(bonusList) == "table" and #bonusList > 0 then
            bonus = bonusList[1];
        end

        -- 暂时仅需要处理获得钻石的情况
        if type(bonus) == "table" and bonus[2] == "gem" then
            local function autoTakeBonus()
                -- 尝试领取限时礼包奖励
                if GiftM.ifAchieveTimeLimitGift() and not GiftM.takeTimeLimitBonusAlready() then
                    -- 发送领取请求
                    Operation.cmd_take_time_gift_bonus();
                else
                    -- 自动领取首充奖励
                    checkFirstChargeBonus();
                end
            end

            local function okFunc()
                -- 网页第三方充值，需要弹出额外获得的钻石
                if type(compensateGem) == "number" and compensateGem > 0 then
                    local extraBonus = { { 2, "gem", compensateGem, } }
                    showBonusNotifyForm(getLocStr("recharge_ok_title"), extraBonus, nil,
                        getLocStr("recharge_web_extra_gem"), nil, nil, nil, autoTakeBonus);
                    return;
                end

                autoTakeBonus();
            end
            showBonusNotifyForm(getLocStr("recharge_ok_title"), bonusList, nil, nil, nil, nil, nil, okFunc);

            -- 关闭等待充值的确认框
            local currentDialog = UIMgr.getCurrentScene():getCurrentDialog();
            if currentDialog and currentDialog.extraPara and
               currentDialog.extraPara.waitingRecharge then
                UIMgr.getCurrentScene():removeDialog(currentDialog);
            end
        end
    end);

    -- 关注购买商品的事件
    EventMgr.register("UIMainMap", event.BUY_GOODS, function(args)
        self:refreshIcons();

        -- PT充值成功提示
        if args.goodsId == PT_RECHARGE_ID then

            -- 充值成功后检查奖励领取
            local function okFunc()
                checkFirstChargeBonus();
            end

            -- pt充值弹出确认提示框
            local amount = args.bonus[1][3] or 1;

            local desc = string.format(getLocStr("recharge_pt_succ"), amount);
            local title = MarketM.query(args.goodsId, "name");

            confirm2(CONFIRM_TYPE_OK, title, desc, okFunc);
        end

        -- 如果是通过pt购买钻石成功，显示奖励提示框
        local goodsInfo = MarketM.getGoodsInfo(args.goodsId);
        if args.pt_buy and goodsInfo["classify"] == "recharge" then
            local uiMarket = UIMgr.getCurrentScene():getFormByName("UIMarketMain");
            if uiMarket then
                -- 界面刷新时，last_charge_time还没有发下来，这里再刷新一下
                local function refreshUI()
                    if uiMarket.curType == "recharge" then
                        uiMarket:showRechargePage(false);

                        -- 如果购买pt成功，更新pt
                        uiMarket:redrawPtTitle(args.goodsId);
                    end
                end

                showBonusNotifyForm(goodsInfo["name"], args.bonus, refreshUI);
            else
                showBonusNotifyForm(goodsInfo["name"], args.bonus);
            end
        end
    end);

    -- 关注迷宫成就完成的事件
    EventMgr.register("UIMainMap", event.ACHIV_COMPLETE, function(args)
        local id = args.id;

        MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_EVENT, {["id"] = id});
    end);

    -- 关注迷宫任务完成的事件
    EventMgr.register("UIMainMap", event.DUNGEON_TASK_COMPLETE, function(args)
        local id = args.id;

        MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_TASK, {["id"] = id});
    end);

    -- loading事件
    EventMgr.register("UIMainMap", event.WAIT_LOADING, function(args)
        showCommunicatingForm(args or getLocStr("loading"));
    end);

    -- 关闭loading事件
    EventMgr.register("UIMainMap", event.CLOSE_WAIT_LOADING, function(args)
        closeCommunicatingForm();
    end);

    -- 关注设置排行榜概要事件
    EventMgr.register("UIMainMap", event.SET_RANK_BRIEF, function(args)
        local result = args.result;

        -- 设置昵称失败，显示提示语
        if result ~= "ok" then
            alert(getLocStr(result));
            return;
        end

        local bonus = FormulaM.invoke("CALA_SET_NICK_BONUS");

        -- 奖励钻石
        if DungeonM.isInDungeon() then
            -- 地牢内客户端直接奖励
            BonusM.doBonus(bonus, "nickname_bonus");

            -- 如果是从评论界面设置的需要弹出提示
            local uiComment = UIMgr.getCurrentScene():getFormByName("UIComment");
            if uiComment then
                showBonusNotifyForm(getLocStr("nickname_bonus"), {bonus});
            end

            -- 设置成功后要关闭界面
            closeFormByName("UIRankNick");
            return;
        end

        -- 设置成功奖励钻石，此处给出提示
        showBonusNotifyForm(getLocStr("nickname_bonus"), {bonus});

        closeFormByName("UIRankNick");
    end);

    -- 关注大地图用排行榜数据更新
    EventMgr.register("UIMainMap", event.NOTIFY_MAIN_MAP_TOP_INFO, function()
        local areas = table.keys(DungeonAreaM.getMainAreas());

        for _, id in pairs(areas) do
            local index = table.indexOf(areas, DungeonAreaM.getParentId(id));
            self:refreshTopInfo(self.areaFlags[index], id);
        end
    end);

    -- 关注国家设置事件
    EventMgr.register("UIMainMap", event.SELECT_NATION, function(args)
        local areas = table.keys(DungeonAreaM.getMainAreas());
        for _, dungeonId in pairs(areas) do
            local index = table.indexOf(areas, dungeonId);
            self:refreshTopInfo(self.areaFlags[index], dungeonId);
        end
    end);

    -- 关注战斗结算结束事件
    EventMgr.register("UIMainMap", event.COMBAT_STAT_FINISHED, function(args)
        local areas = table.keys(DungeonAreaM.getMainAreas());
        local dungeonId = args.dungeon_id;

        local index = table.indexOf(areas, DungeonAreaM.getParentId(dungeonId));
        if index == -1 then
            -- 不是主关卡，不处理
            return;
        end

        local dungeonScore = ME.user.dbase:query("dungeon_score")[dungeonId];
        local dungeonLayer = args.layer;
        local topInfo      = TopM.getTopData(dungeonId);
        local mmTopInfo    = TopM.getMainMapTopData(dungeonId);
        local topPlayerInfo;

        if #topInfo > 0 then
            topPlayerInfo = topInfo[1];
        else
            topPlayerInfo = mmTopInfo;
        end

        local topLayer = topPlayerInfo.layer or 0;
        local topScore = topPlayerInfo.score or 0;

        if dungeonLayer < topLayer or
           dungeonLayer == topLayer and dungeonScore < topScore then
           return;
        end

        self:refreshTopInfo(self.areaFlags[index], dungeonId);
    end);

    -- 关注领取区域解锁奖励事件
    EventMgr.register("UIMainMap", event.TAKE_UNLOCK_BONUS, function(args)
        local bonusList = args.bonusList;
        local areaId    = args.areaId;

        -- 播放锁头爆开的特效
        local function playLockOpen()
            local function showBonus()
                -- 更新一下地图图标
                self:refreshIcons();

                -- 弹出奖励提示框
                local name = DungeonAreaM.query(areaId, "name");
                local desc = string.format(getLocStr("area_unlock_bonus_tip"), name);
                if bonusList ~= nil and #bonusList > 0 then
                    showBonusNotifyForm(name, bonusList, nil, desc);
                end
            end

            -- 等锁头爆开后再弹出奖励提示框
            performWithDelay(self, showBonus, 1.0);

            local areas = table.keys(DungeonAreaM.getMainAreas());
            local index = table.indexOf(areas, areaId);
            local flagNode = self.areaFlags[index];
            local tipImg = findChildByName(flagNode, "flag/rescue/tip");
            tipImg:setVisible(false);
            local effectNode = findChildByName(flagNode, "flag/lock_effect");
            playAreaLockOpenEffect(effectNode);
        end
        performWithDelay(self, playLockOpen, 0.2);
    end);

    EventMgr.register("UIMainMap", event.TOP_DATA_UPDATED, function()
        -- 更新一下地图图标
        self:refreshIcons();
    end);

    -- 邮件信息刷新
    EventMgr.register("UIMainMap", event.UPGRADE_MAILS, function(para)
        self:checkMailBox();
    end);

    -- 收到新的邮件
    EventMgr.register("UIMainMap", event.RECEIVE_MAILS, function(para)
        self:checkMailBox();
        local uiForm = UIMgr:getCurrentScene():getFormByName("UIDungeonMain");
        if uiForm then
            -- 如果玩家当前在迷宫中，则弹提示
            MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_MAIL, {["id"] = 1});
        end
    end);

    -- 关注获得广告奖励事件
    EventMgr.register("UIMainMap", event.AD_BONUS_DONE, function(args)
        local bonusList = args.bonusList;
        local bonusType = args.bonusType;
        if bonusList ~= nil and #bonusList > 0 then
            local title = "";
            if #bonusList == 1 then
                title = getLocStr("watch_ad_bonus_tip");
            else
                title = getLocStr("watch_click_ad_bonus_tip");
            end

            local function takeBonus()
                -- 通知服务器给奖励
                Operation.cmd_do_ad_bonus(bonusType);
            end

            showBonusNotifyForm(getLocStr("slime_tv_title"), bonusList,
                takeBonus, title, getLocStr("take_bonus"));
        end
    end);

    -- 注册成就完成的事件回调
    EventMgr.register("UIMainMap", event.GAME_ACHIV_COMPLETE, function(para)
        self:checkAchiv();

        -- 如果该项成就已经圆满，弹出提示
        local id = para.id;
        local maxRank = GameAchievementM.getMaxRank(id);
        print("curRank:"..GameAchievementM.query(id ,"rank")..",maxRank:"..maxRank);
        if GameAchievementM.query(id ,"rank") == maxRank then
            print("MSG_REMINDER_TYPE_ACHIEVEMENT")
            MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_ACHIEVEMENT, {["id"] = id});
        end
    end);

    -- 注册通关迷宫的事件回调
    EventMgr.register("UIMainMap", event.DUNGEON_PASS, function(para)
        self:checkAchiv();
        self:redrawActivity();
        self:checkActivity();
        self:checkFate();
    end);

    -- 关注服务器状态更新事件
    EventMgr.register("UIMainMap", event.SERVER_STATUS_UPDATE, function(para)
        local data = para.data;
        if data["forbid_rotine_activity"] then
            -- 常规活动开放/禁止
            self:redrawActivity();
            self:checkActivity();
        end

        if data["fate_chain_open"] then
            -- 命运之链开放/禁止
            self:checkFate();
        end

        if data["awake"] then
            -- 觉醒系统开放/禁止
            self:checkAwake();
        end
    end);

    -- 注册G+登录提醒事件的回调
    EventMgr.register("UIMainMap", event.REMIND_CONNECT_GOOGLE, function(args)
        showConnectGoogleDialog(getLocStr("google_center_login_title"),
            getLocStr("google_center_login_tip"));
    end);

    -- 注册亚马逊登录提醒事件的回调
    EventMgr.register("UIMainMap", event.REMIND_CONNECT_AMAZON, function(args)
        showConnectAmazonDialog(getLocStr("amazon_center_login_title"),
            getLocStr("amazon_center_login_tip"));
    end);

    -- 已提醒指引物品
    EventMgr.register("UIMainMap", event.REMIND_FEATURE, function(args)
        if args.ruleName == "key" then
            self.remindFeatureEffect = "key";
        end
    end);

    -- 关注谷歌账号权限声明事件
    EventMgr.register("UIMainMap", event.SHOW_PERMISSION_EXPLAIN, function(callback)
        showPermissionExplainUI(callback);
    end);

    -- 关注第三方渠道账号登录成功的事件
    EventMgr.register("UIMainMap", event.THIRD_ACCOUNT_LOGINED, function(args)
        if DungeonM.isInDungeon() then
            -- 如果在迷宫中，不处理
            return;
        end

        local channel   = args.channel;
        local loginInfo = args.loginInfo;
        local account = tostring(loginInfo.account);
        local realAccount = string.format("%s_%s", channel, account);
        local gameAccount = AccountM.getGamingAccount();
        if realAccount == gameAccount then
            -- 登录的就是游戏中的账号
            return;
        end

        if string.startWith(gameAccount, channel) == true then
            -- 切换账号了，直接踢出去
            ME.isInGame = false;
            uiMainMap = UIMgr.getCurrentScene():getFormByName("UIMainMap");
            uiMainMap:quitingGame({ ["autoLogin"] = true });
        end
    end);

    -- 关注第三方账号注销的事件
    EventMgr.register("UIMainMap", event.THIRD_ACCOUNT_LOGOUT, function(args)
        -- 退出到登录界面
        uiMainMap = UIMgr.getCurrentScene():getFormByName("UIMainMap");
        uiMainMap:quitingGame({ });
    end);

    -- 关注月卡领奖事件
    EventMgr.register("UIMainMap", event.TAKE_SUPERIOR_BONUS, function(args)
        local level = args.level;
        local bonus = args.bonus;
        local title = SuperiorM.query(level, "name");

        -- 关闭界面时，抛出确认月卡领奖事件
        local function exitFunc()
            EventMgr.fire(event.UI_TAKE_SUPERIOR_BONUS, { ["level"] = level, });
        end

        showBonusNotifyForm(title, bonus, nil, nil, nil, nil, nil, exitFunc);
    end);

    -- 关注炼金坊购买装备事件
    EventMgr.register("UIMainMap", event.WORKSHOP_BUY_EQUIP, function()
        -- 隐藏标题
        local workshopForm = UIMgr.getCurrentScene():getFormByName("UINewWorkshop");
        if workshopForm then
            workshopForm:hideTitle();
        end

        -- 获得第一个英雄
        local classId = 1;
        ME.user.dbase:setTemp("gain_pet_id", classId);
        local uiForm = UIHeroGainEffect.create(classId);
        UIMgr.getCurrentScene():addForm(uiForm);

        UIMgr.getCurrentScene():removeFormByName("UIGuideBuyEquip");
    end);

    -- 关注领取分享奖励的事件
    EventMgr.register("UIMainMap", event.SHARE_BONUS, function(args)
        local bonus = args.bonus;
        local title = getLocStr("share_succ");
        local msg   = getLocStr("gain_bonus_tip");
        showBonusNotifyForm(title, bonus, nil, msg);
    end);

    -- 注册获得物品回调
    EventMgr.register("UIMainMap", event.ITEM_COUNT_UPDATED, function(classId)
        -- 活动关注的物品
        if table.indexOf(self:forcusActivityScraps(), classId) >= 0 then
            self:checkActivity();
        end
    end);

    -- 关注活动信息变化的事件
    EventMgr.register("UIMainMap", event.ACTIVITY_CHANGE, function(args)
        self:redrawActivity();
        self:checkActivity();
    end);

    -- 关注活动关闭的事件
    EventMgr.register("UIMainMap", event.ACTIVITY_CLOSE, function(args)
        self:redrawActivity();
        self:checkActivity();
    end);

    updateBlackMarketPos();

    -- 关注账号绑定提示事件
    EventMgr.register("UIMainMap", event.GUEST_ACCOUNT_NEED_BIND, function(callback)
        local function onOK()
            -- 弹出绑定账号界面
            UIMgr.getCurrentScene():removeFormByName("UIUIAccountBind");
            local uiAccountBind = UIAccountBind.create();
            UIMgr.getCurrentScene():addForm(uiAccountBind);
        end

        local function onCancel()
            if callback then
                callback();
            end
        end

        confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("guest_mode"), getLocStr("guest_mode_tip2"),
            onOK, onCancel, getLocStr("btn_text_bind"), getLocStr("btn_text_cancel"));
    end);

    -- 触发对话
    EventMgr.register("UIMainMap", event.TRIGGER_ATHENE_DIALOG, function(args)
        local function callback()

            local function okFunc()

                if not ReagentM.canBuyAthena() then
                    showGemLackConfirm();

                    return;
                end

                UIMgr.getCurrentScene():removeFormByName("UIBuyAthena");
                DungeonActionM.go("buy_athena_formula");

                startDialogueTemplate("athena_formula2");
            end

            local function cancelFunc()
                UIMgr.getCurrentScene():removeFormByName("UIBuyAthena");
                startDialogueTemplate("athena_formula3");
            end

            local title = getLocStr("ahena_tear_title");
            local desc = getLocStr("athena_formula_desc");
            local okText = getLocStr("btn_text_give");
            local cancelText = getLocStr("btn_text_not_give");

            require "game/ui/form/reagent/UIBuyAthena";

            UIMgr.getCurrentScene():removeFormByName("UIBuyAthena");

            local uiForm = UIBuyAthena.create(CONFIRM_TYPE_OK_CANCEL, title, desc, okFunc, cancelFunc, okText, cancelText,
                    {["backFunc"] = cancelFunc, ["hideBackBtn"] = true});

            UIMgr.getCurrentScene():addForm(uiForm);
        end

        startDialogueTemplate("athena_formula1", callback);
    end);

    -- 注册定位相关回调事件
    self:registerLocationEventCallback();

    -- 界面析构后，需要清理下
    self:registerScriptHandler(function(event)
        if event == "exit" then
            ME.user.dbase:removeCb("UIMainMap", { "dungeon_progress", });
            EventMgr.removeAll("UIMainMap");
        elseif event == "enter" then
            -- 清除无用纹理
            UIMgr.clearUnusedTextures();

            -- 请求数据
            -- SLIMEC-351原解决方法,该方法不可取屏蔽
            -- SLIMEC-351中已修改
            -- Operation.cmd_refresh_items();

            -- 彩蛋系统, 本身也是个node, 不可见，挂在mainMap上
            self.eggs = UIEggs.create();
            self:addChild(self.eggs);
            self.eggs:displayEggs();

            -- 注册back按键的处理函数
            self:initBackEventHandler();

            Operation.cmd_enter_world();

            -- 完成进入大地图的引导（只有第8步引导过了才能完成）
            local guideBuyId = 8;
            local guideMapId = 9;
            if AlchemyWorkshopM.isGuideFinished(guideBuyId) and
               not AlchemyWorkshopM.isGuideFinished(guideMapId) then
                Operation.cmd_set_workshop_guide_progress(guideMapId);
            end
        elseif event == "enterTransitionFinish" then
            -- 默认显示地图最底部内容
            local map = findChildByName(self.node, "map");
            map:jumpToBottom();

            if self:isVisible() then
                self:topAndBottomMenuFlyIn();
            end
        end
    end)
end

-- 注册定位相关回调事件
function UIMainMap:registerLocationEventCallback()
    -- 关注定位失败的事件
    EventMgr.register("UIMainMap", event.GET_LOCATION_FAIL, function(reason)
        local msg = string.format(getLocStr("get_location_fail"), tostring(reason));
        confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("system_prompt"), msg);
    end);

    -- 关注使用定位功能描述的事件
    EventMgr.register("UIMainMap", event.LOCATION_PERMISSION_DESC, function(callback)
        local function onOK()
            if callback then
                callback();
            end
        end

        local function onCancel()
            local function showMsg()
                confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("system_prompt"), getLocStr("no_location_permission"));
            end
            performWithDelay(self, showMsg, 0.1);
        end

        confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("system_prompt"), getLocStr("location_perssion_desc"),
            onOK, onCancel, nil, nil, {["hideBackBtn"]=true})
    end);

    -- 关注安卓下提示打开GPS定位功能的事件
    EventMgr.register("UIMainMap", event.ANDROID_ENABLE_GPS_DESC, function()
        local function onOK()
            if isAndroidPlatform() then
                LocationAgent.enableService();
            end
        end

        confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("system_prompt"), getLocStr("to_enable_gps_desc"), onOK);
    end);

    -- 关注IOS下提示打开GPS定位功能的事件
    EventMgr.register("UIMainMap", event.IOS_ENABLE_GPS_DESC, function()
        local function onOK()
            if isIosPlatform() then
                local version = tonumber(getOSVersion());
                if version >= 10 then
                    -- ios10.0需要通过直接发起定位来弹出设置窗口
                    LocationAgent.requestAuthorization();
                else
                    LocationAgent.enableService();
                end
            end
        end

        confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("system_prompt"), getLocStr("to_enable_gps_desc"), onOK);
    end);

    -- 关注安卓下提示定位功能被拒绝的事件
    EventMgr.register("UIMainMap", event.ANDROID_LOCATION_PERMISSION_DENIED, function()
        confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("system_prompt"), getLocStr("no_location_permission"));
    end);

    -- 关注安卓下提示定位功能被永久拒绝的事件
    EventMgr.register("UIMainMap", event.ANDROID_LOCATION_PERMISSION_DISABLED, function()
        local function onOK()
            if isAndroidPlatform() then
                -- 功能被永久拒绝了，此处打开设置窗口
                LocationAgent.openPemissionSetting();
            end
        end

        local function onCancel()
            local function showMsg()
                confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("system_prompt"), getLocStr("no_location_permission"));
            end
            performWithDelay(self, showMsg, 0.1);
        end

        confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("system_prompt"), getLocStr("location_perssion_desc"),
            onOK, onCancel, getLocStr("btn_text_open"), nil, {["hideBackBtn"]=true})

    end);

    -- 关注IOS下提示定位功能被禁止的事件
    EventMgr.register("UIMainMap", event.IOS_NO_LOCATION_PERMISSION, function()
        confirm2(CONFIRM_TYPE_OK_NOBACK, getLocStr("system_prompt"), getLocStr("no_location_permission"));
    end);
end

-- 处理MESSAGE_DONE消息
function UIMainMap:handleMessageDone()
    local para  = self.messageDonePara;

    -- 如果是“在地牢中”消息
    if para[1] == "MSG_IN_DUNGEON" then
        if not DungeonM.isInDungeon() then
            self:checkInDungeonState();
            return;
        end
        -- self.inDungeonId = para[2].dungeon_id;
    elseif para[1] == "MSG_QUITING" then
        -- 退出游戏
        local msg;
        if para[2] and para[2].data and type(para[2].data.reason) == "string" then
            msg = getDialogStr(para[2].data.reason);
        end

        if not msg then
            msg = getLocStr("quit_game_confirm");
        end
        confirm2(CONFIRM_TYPE_OK, getLocStr("quit_game"), msg,
            quitingConfirm, nil, nil, nil, { ["backFunc"] = quitingConfirm,});

    elseif para[1] == "MSG_END_SYNC" and para[2].result == 0 then
        -- 先显示通信中，以便上传错误日志，10秒后出现同步异常提示
        showCommunicatingForm(getLocStr("waiting"));

        local function callback()
            -- 关闭通信中界面
            closeCommunicatingForm();

            -- 如果是测试战斗
            if TestFightM.isInTest() then
                -- 设置自动测试
                quitingConfirm({["autoLogin"] = true});
                return;
            end

            -- 弹出同步异常确认框
            local msg = getLocStr("sync_error");

            confirm2(CONFIRM_TYPE_OK, getLocStr("sync_error_title"), msg,
                quitingConfirm, nil, nil, nil, { ["backFunc"] = quitingConfirm});
        end

        performWithDelay(self, callback, 10);
    elseif para[1] == "MSG_RELOGIN" and para[2].code == 0 then
        -- 重登陆失败
        confirm2(CONFIRM_TYPE_OK, getLocStr("quit_game"), getLocStr("disconnect"),
            quitingConfirm, nil, nil, nil, { ["backFunc"] = quitingConfirm,});
    elseif para[1] == "MSG_ENTER_WORLD" then
        -- 自动战斗测试
        if TestFightM.isInTest() then
            -- 模块里处理了自动进入地牢，这里不需要下面这些
            return;
        end

        -- 获取到MSG_ENTER_WORLD后才允许操作
        closeCommunicatingForm();

        local function doEnterWorldCheck()
            -- 在获取服务器时间后按顺序处理公告提示、炼金提示和进副本提示
            -- 检查公告信息/未完成新手指引不弹出
            local boardId = GameBoardM.getLastNotReadId();
            if (tonumber(boardId) ~= 0) and AlchemyWorkshopM.isAllGuideFinished() then
                -- 有公告需要显示
                showGameBoard(boardId);
                self.openGameBoard = true;
                return;
            end

            -- 检查一次炼金奖励
            self:confirmWorkshopBonus();
        end

        -- 检查是否下载独立资源包, 设置了失败重试标记
        checkExtraPackDownload(doEnterWorldCheck, true);
    end
end

-- 检查炼金奖励
function UIMainMap:confirmWorkshopBonus()
    -- 检查一次炼金奖励
    if not self.checkWorkshopBonus then
        local text, count = AlchemyWorkshopM.fetchBonusText();
        count = count or 0;
        if text ~= "" then
            -- 有炼金消息的话先显示炼金消息
            local confirmForm;
            if count < 2 then
                confirmForm = confirm2(CONFIRM_TYPE_OK, getLocStr("workshop_bonus_tip_title"), text, self.confirmEnterDungeon, nil, nil, nil, { ["backFunc"] = self.confirmEnterDungeon,});
            else
                confirmForm = confirm4(CONFIRM_TYPE_OK, getLocStr("workshop_bonus_tip_title"), text, self.confirmEnterDungeon, nil, nil, nil, { ["backFunc"] = self.confirmEnterDungeon,});
            end
            AudioM.playFx("button_spell");
            return;
        end
        self.checkWorkshopBonus = true;
    end

    self:confirmEnterDungeon();
end

-- 检查地牢提示
function UIMainMap:confirmEnterDungeon()
    local currentDungeon = ME.user.dbase:query("currentDungeon");
    if currentDungeon ~= nil then
        local function okFunc()

            -- 迷宫外显示通信中界面
            local uiForm = UIMgr:getCurrentScene():getFormByName("UIDungeonMain");
            if uiForm then
                return;
            end

            showCommunicatingForm(nil, false, false, nil, "enter_dungeon");
            enterDungeon(currentDungeon);
        end

        local function cancelFunc()
            cancelDungeon(currentDungeon);
        end

        local function pauseFunc()
            -- 给服务器发暂离请求
            Operation.cmd_apply_pause_dungeon_state();

            -- 暂离, 设置下主界面的暂离状态
            -- 添加暂离提示
            MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_PAUSE, {["id"] = 1});

            UIMgr.getCurrentScene().mainMap:checkPauseState();
            UIMgr.getCurrentScene().bottomMenu:checkPauseState();
            UIMgr.getCurrentScene().topMenu:checkPauseState();
            DungeonM.switchState(DUNGEON_PAUSING);
        end

        -- 如果允许暂离，提供“继续”和“暂时离开”两个选项
        if DungeonM.canPauseDungeon() then
            confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_note"), getLocStr("continueDungeon"),
                okFunc,
                pauseFunc,
                getLocStr("btn_text_continue_dungeon"),
                getLocStr("btn_text_pause_dungeon"),
                { ["okColor"] = "blue", ["backStyle"] = "yellow",
                ["backText"] = getLocStr("btn_text_leave_dungeon"),["backFunc"] = cancelFunc, } );
        else
            confirm2(CONFIRM_TYPE_OK, getLocStr("slime_note"), getLocStr("continueDungeon"),
                okFunc,
                nil,
                getLocStr("btn_text_continue_dungeon"),
                nil,
                { ["okColor"] = "blue", ["backStyle"] = "yellow",
                    ["backText"] = getLocStr("btn_text_leave_dungeon"),["backFunc"] = cancelFunc, } );
        end

        AudioM.playFx("button_spell");

        -- 还原背景
        if UIMgr.getCurrentScene().mainMap ~= nil then
            UIMgr.getCurrentScene().mainMap:setNormalColor();
            UIMgr.getCurrentScene().bottomMenu:setNormalColor();
            UIMgr.getCurrentScene().topMenu:setNormalColor();
        end
    end
end

-- 检查限时礼包置灰
function UIMainMap:checkLimitGiftBonus(force)
    if self.uiMsgReminder then
        self.uiMsgReminder:checkGray(force);
    end
end

-- 注册back按钮的处理函数
function UIMainMap:initBackEventHandler()
    exitGamePrompt(self);
end

-- 检查炼金产出
function UIMainMap:checkAlchemyProduction()
    -- 用于第一次进入函数判断
    self.first = true;

    local function tick()
        local interval = FormulaM.invoke("GET_ALCHEMY_PRODUCE_INTERVAL", ME.user) + 1;
        local nowTime = TimeM.getCurrentTime();
        local alchemyTime = AlchemyWorkshopM.getStartTime();

        if self.first == true then
            -- 第一次tick()调用，需要重设StartTime
            AlchemyWorkshopM.setStartTime(nowTime + 1);
            self.first = false;
            nowTime = TimeM.getCurrentTime();
            alchemyTime = AlchemyWorkshopM.getStartTime();
        end

        if nowTime - alchemyTime > interval then
            -- 之前进入了暂停状态，需要重设StartTime
            AlchemyWorkshopM.setStartTime(nowTime + 1);
            nowTime = TimeM.getCurrentTime();
            alchemyTime = AlchemyWorkshopM.getStartTime();
        end

        local elapse = math.floor(nowTime - alchemyTime) % interval;

        local leftTime = interval - elapse;

        local min = math.floor(leftTime / 60);
        local sec = leftTime % 60;

        EventMgr.fire(event.WORKSHOP_LEFT_TIME, { ["min"] = min, ["sec"] = sec - 1 });
        self.workShopLeftTime = { ["min"] = min, ["sec"] = sec - 1 };
        if leftTime <= 1 then
            -- 玩家在迷宫中不请求
            local uiForm = UIMgr:getCurrentScene():getFormByName("UIDungeonMain");
            if uiForm then
                -- cclog("玩家在迷宫中，不请求炼金产出！");
            else
                -- 向服务器请求数据
                Operation.cmd_check_workshop();
                -- 重设StartTime
                AlchemyWorkshopM.setStartTime(nowTime + 1);
            end
        end

        performWithDelay(self, tick, 1);
    end

    tick();
end

-- 连接消息管理界面
function UIMainMap:updateMsgReminderUI()
    if not self.uiMsgReminder then
        self.uiMsgReminder = UIMsgReminder.create(true);
        local msgReminderNode = findChildByName(self.node, "msg_reminder");
        msgReminderNode:addChild(self.uiMsgReminder);
    end

    self.uiMsgReminder:update();
end

-- 合成成功的事件回调
function whenDoCompound(args)
    if args.isAuto ~= true then
        -- 不是自动合成不处理
        return;
    end

    local product = args.product;
    if #product <= 0 then
        -- 没有产物？
        return;
    end

    if #product == 1 then
        -- 仅一种产物
        local arr = product[1];
        if arr[1] == 1 then
            local name = ItemM.query(arr[2], "name");
            local msg = string.format(getLocStr("auto_compound_ok"), name, arr[3]);
            showHint(msg, false);
        elseif arr[1] == 2 then
            local name = FieldsM.getFieldName(arr[2]);
            local msg = string.format(getLocStr("auto_compound_ok"), name, arr[3]);
            showHint(msg, false);
        end
    else
        -- 有多种产物
        -- TODO 暂时不支持，有需要再添加
    end
end

-- 取消进入地牢
function cancelDungeon(dungeonId)
    local function okFunc()
        UIDungeonMgr.leaveDungeon();
    end

    local function cancelFunc()
        local function enter()
            enterDungeon(dungeonId)
        end

        local function leave()
            cancelDungeon(dungeonId);
        end

        local function pause()
            -- 给服务器发暂离请求
            Operation.cmd_apply_pause_dungeon_state();

            -- 添加暂离提示
            MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_PAUSE, {["id"] = 1});

            -- 暂离, 设置下主界面的暂离状态
            UIMgr.getCurrentScene().mainMap:checkPauseState();
            UIMgr.getCurrentScene().bottomMenu:checkPauseState();
            UIMgr.getCurrentScene().topMenu:checkPauseState();
            DungeonM.switchState(DUNGEON_PAUSING);
        end

        -- 如果允许暂离
        if DungeonM.canPauseDungeon() then
            confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_note"), getLocStr("continueDungeon"),
                enter,
                pause,
                getLocStr("btn_text_continue_dungeon"),
                getLocStr("btn_text_pause_dungeon"),
                { ["okColor"] = "blue", ["backStyle"] = "yellow",
                    ["backText"] = getLocStr("btn_text_leave_dungeon"),["backFunc"] = leave, } );
        else
            confirm2(CONFIRM_TYPE_OK, getLocStr("slime_note"), getLocStr("continueDungeon"),
                enter,
                nil,
                getLocStr("btn_text_continue_dungeon"),
                nil,
                { ["okColor"] = "blue", ["backStyle"] = "yellow",
                    ["backText"] = getLocStr("btn_text_leave_dungeon"), ["backFunc"] = leave, } );
        end
    end

    confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("leave_dungeon"),
        getLocStr("leave_dungeon_confirm_again"), okFunc, cancelFunc, nil, nil,
        { ["backFunc"] = cancelFunc, });
end

-- 确认进入地牢
function enterDungeon(dungeonId)
    DungeonAreaM.setCurrentArea(ME.user, dungeonId);
    DungeonServiceM.startEnterService(dungeonId);
end

-- 退出游戏确认框
function quitingConfirm(para)
    wait(1);
    UIMgr.getCurrentScene():setVisible(false);

    Socket.disconnect();

    -- 重新初始化模块
    loadModules();

    ME.isInGame = false;

    if type(para) == "table" and para["autoLogin"] == true then
        -- 设置自动登陆
        LoginM.setAutoLogin(true);
    end

    -- 退出到登录界面
    UIMgr.showLogin();
    DungeonServiceM.clearMsgQue();
    UIDungeonMgr.clearCurLevel();
end

-- 退出调用
function UIMainMap:quitingGame(para)
    quitingConfirm(para);
end

-- 玩家操作定时检测
function UIMainMap:checkUserAction()
    -- 如果是自动战斗中，或者播放中，忽略
    if StatusM.check(ME.user, "PLAYING_RECORD") or AutoFightM.inFighting() or TestFightM.isInTest() then
        return;
    end

    if self.isActionTick then
        return;
    end

    self.isActionTick = true;
    local function tick()
        if TestFightM.isInTest()
            or AutoOperationM.isInAutoOperation() then
            -- 自动测试，不管
            return;
        end

        -- 累计时间（如果玩家此时正在下载资源包，则不累加时间）
        if not UIMgr.getCurrentScene():isOpen("UIPkgDownload") then
            FormFocusM.setUserIdleTime(FormFocusM.getUserIdleTime() + 10);
        end

        if FormFocusM.getUserIdleTime() >= USER_IDLE_LIMIT_TIME then
            -- 设置不显示一般的重连信息
            LoginM.setShowDisconnectTip(false);
            -- 断开连接
            Socket.disconnect();
            self.isActionTick = false;
            local function okFunc()
                -- 显示重连中
                showCommunicatingForm();

                -- 重连
                LoginM.reConnect();
            end

            local function cancelFunc()
                -- 直接踢出去
                ME.isInGame = false;

                -- 重新初始化模块
                loadModules();

                -- 退出到登录界面
                UIMgr.showLogin();

                DungeonServiceM.clearMsgQue();
                UIDungeonMgr.clearCurLevel();
            end

            -- 显示提示框
            confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_note"), getLocStr("auto_disconnect_tip"),
                okFunc, cancelFunc, getLocStr("btn_text_ok"), getLocStr("btn_text_cancel"), { ["cancelColor"] = "blue",
                    ["hideBackBtn"] = true });
        else
            -- 10s回调增加时间一次
            performWithDelay(self, tick, 10);
        end
    end
    tick();

    -- 检查WEB公告的回调
    local function webTick()
        GameBoardM.WebcheckGameBoard();
        -- 1小时检查一次
        performWithDelay(self, webTick, WEB_BOARD_CHECK);
    end
    -- 首次进入主地图检查一次
    webTick();
end

-- 初始化地图
function UIMainMap:initMap()
    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;

    local map = findChildByName(self.node, "map");

    -- 进入地牢
    local function tryEnterDungeon(areaId)
        DungeonAreaM.setCurrentArea(ME.user, areaId);

        if not DungeonAreaM.isAreaUnlocked(ME.user, areaId) then
            alert(getLocStr("area_is_not_unlocked"));
            return;
        end

        if UIMgr:getCurrentScene():isOpen("UISubAreaList") then
            UIMgr:getCurrentScene():removeFormByName("UISubAreaList");
        end

        -- 进入地牢
        if DungeonM.isInDungeon() or DungeonM.isPause() then
            -- 显示通信中界面
            showCommunicatingForm();

            -- 暂离状态，直接进入
            local id = ME.user.dbase:query("current_dungeon");
            DungeonServiceM.startEnterService(id);

            -- 开始游戏后不可再操作,添加一层挡板
            -- local uiForm = UIInvisibleForm.create();
            -- uiForm:setName("SeperatorForm");
            -- UIMgr.getCurrentScene():addForm(uiForm);
            return;
        end

        local uiEnter = UISubAreaList.create(areaId);
        UIMgr:getCurrentScene():addForm(uiEnter);
    end

    -- 初始化旗子
    self.areaFlags = {};
    self.flagPosList = {};
    local areas = table.keys(DungeonAreaM.getMainAreas());
    local dungeonProgress = ME.user.dbase:query("dungeon_progress", {});

    for i = 1, #areas do
        local coords = string.explode(DungeonAreaM.query(areas[i], "flag_coords"), ",");
        local posX = tonumber(coords[1]);
        local posY = tonumber(coords[2]);

        local flagNode = cc.CSLoader:createNode("layout/main/Flag.csb");
        flagNode = replaceNodeWithWidget(flagNode);

        -- 增加地牢名字显示
        local rescueNode = findChildByName(flagNode, "flag/rescue");
        local nameNode  = findChildByName(flagNode, "flag/rescue/icon");
        local titleNode  = findChildByName(flagNode, "flag/rescue/tilte");
        local areaId = areas[i];

        local areaType = DungeonAreaM.query(areaId, "type");
        if areaType == "main" or areaType == "dlc" then
            local slime = DungeonAreaM.getAreaSlime(areaId);
            if slime ~= nil then
                local heroId = table.values(slime)[1];
                local icon = PetM.getDefaultIcon(heroId);
                if icon ~= nil then
                    local heroIconPath = getHeroIconPath(icon);
                    nameNode:loadTexture(heroIconPath);
                    nameNode:setScale(0.7);
                    rescueNode:setVisible(true);
                end
            end
        else
            rescueNode:setVisible(false);
        end

        -- 增加排行榜显示
        self:refreshTopInfo(flagNode, areaId);

        -- 提示信息
        local tipImg = findChildByName(flagNode, "flag/rescue/tip");

        -- 如果解锁奖励尚未领取
        if not DungeonAreaM.isUnlockBonusTaken(areaId) and
           not DungeonAreaM.isSubDungeonPassed(ME.user, areaId) then
            -- 显示锁头
            tipImg:setVisible(true);
            tipImg:loadTexture("images/ui/main/lock.png");
            playBubbleAnima(tipImg, false, 0.6);
        else
            -- 关卡未通关或者有奖励未领取时显示tip
            if not DungeonAreaM.isAllSubAreaPassed(ME.user, areaId) or
                not DungeonAreaM.isAllSubAreaBonusTaken(ME.user, areaId) or AchievementM.canTakeBonus(areaId) then
                tipImg:setVisible(true);
                tipImg:loadTexture("images/ui/main/hint2.png", 1);
                playBubbleAnima(tipImg, false, 0.6);
            else
                tipImg:setVisible(false);
            end
        end

        -- 注册点击处理
        local flag = findChildByName(flagNode, "flag");

        flag:setTag(areas[i]);

        flag:addTouchEventListener(function(sender,eventType)
            local function callback(forceGrade)
                self:onForcePress(sender, forceGrade);
            end

            local function cancelCallback()
                sender.forceGrade2Handled = false;
                sender.forceGrade4Handled = false;

                stopForcePressButtonEffect();

                local downTime  = 0.25/2;
                local scaleUp = cc.ScaleTo:create(downTime, 1);
                flag:runAction(scaleUp);
            end

            -- 处理长按
            if handleForcePress(sender, eventType, callback, cancelCallback) then
                return;
            end

            if eventType == ccui.TouchEventType.began then
                -- 1.变小
                local downTime  = 0.25/2;
                local scaleDown = cc.ScaleTo:create(downTime, 0.8);
                flag:runAction(scaleDown);
            elseif eventType == ccui.TouchEventType.ended then
                -- 2.变大
                local downTime  = 0.25/2;
                local scaleUp = cc.ScaleTo:create(downTime, 1);
                flag:runAction(scaleUp);
            elseif eventType == ccui.TouchEventType.canceled then
                -- 3.变大
                local downTime  = 0.25/2;
                local scaleUp = cc.ScaleTo:create(downTime, 1);
                flag:runAction(scaleUp);
            end

            local areaId = sender:getTag();
            local curDungeonId = DungeonM.getDungeonId();
            if eventType == ccui.TouchEventType.ended then
                AudioM.playFx("button_click");

                -- 如果尚未领取解锁奖励并且没有子关卡通关过
                if not DungeonAreaM.isUnlockBonusTaken(areaId) and
                   not DungeonAreaM.isSubDungeonPassed(ME.user, areaId) then
                    -- 显示dp信息
                    showDpInfo(areaId);
                    return;
                end

                -- -- 暂离限制
                -- if DungeonM.isPause() and areaId ~= DungeonAreaM.getParentId(curDungeonId) then
                --     alert(getLocStr("pausing_forbid"));
                --     return;
                -- end

                -- if DungeonM.isInDungeon() then
                --     local dungeonId = ME.user.dbase:query("current_dungeon");
                --     if areaId ~= DungeonAreaM.getParentId(dungeonId) then
                --         alert(getLocStr("pausing_forbid"));
                --         return;
                --     end
                -- end

                -- 获取区域信息
                -- tryEnterDungeon(areaId);


                local uiEnter = UISubAreaList.create(areaId);
                UIMgr:getCurrentScene():addForm(uiEnter);
            end
        end);

        -- 算出rescue坐标，以其为中心点，扩大点击区域
        local panelSize = nameNode:getContentSize();
        local panelWidth = panelSize.width;
        local panelHeight = panelSize.height;
        local areaScale = 0.91;
        local areaX = panelWidth * areaScale;
        local areaY = panelHeight * areaScale;
        local rescuePos = string.explode(DungeonAreaM.query(areas[i], "rescue_pos"), ",");
        local rescuePosX = tonumber(rescuePos[1]);
        local rescuePosY = tonumber(rescuePos[2]);

        posX = posX - areaX / 2 + rescuePosX;
        posY = posY - areaY / 2 + rescuePosY;
        flagNode:setPosition(posX, posY);
        flagNode:setVisible(false);
        flag:setContentSize(areaX, areaY);
        rescueNode:setPosition(areaX / 2, areaY / 2);
        map:addChild(flagNode);
        self.areaFlags[i] = flagNode;
        self.flagPosList[i] = cc.p(posX, posY);
    end

    map:addEventListener(function (sender, eventType)
        if eventType == ccui.ScrollviewEventType.scrolling then
            -- 滑动的时候停止播放地图特效，0.2秒后自动恢复
            if self.isEffectPlaying then
                self:stopEffect();
            end

            local function resume()
                self:resumeEffect();
            end

            map:stopAllActions();
            performWithDelay(map, resume, 0.5);
        end
    end);
end

-- 3Dtouch触发
function UIMainMap:onForcePress(sender, forceGrade)
    if forceGrade >= FORCE_GRADE_4 and not sender.forceGrade4Handled then
        Vibrator.vibrate(1);
        stopForcePressButtonEffect();

        -- 显示迷宫成就界面
        local areaId = sender:getTag();
        UIMgr.getCurrentScene():removeFormByName("UIDungeonAchiv");
        local uiDungeonAchiv = UIDungeonAchiv.create(areaId);
        UIMgr.getCurrentScene():addForm(uiDungeonAchiv);
        AudioM.playFx("ui_open1");

        sender.forceGrade4Handled = true;
    elseif forceGrade >= FORCE_GRADE_3 and not sender.forceGrade2Handled then
        local rescueNode = findChildByName(sender, "rescue");

        local rescueNodeCopy = ccui.Widget:create();
        local children = rescueNode:getChildren();
        local len = table.getn(children)
        for i = 1, len do
            local child = children[i];
            if child:getName() ~= "tip" then
                rescueNodeCopy:addChild(child:clone());
            end
        end

        rescueNodeCopy:setPosition(rescueNode:getPosition());
        rescueNode:getParent():addChild(rescueNodeCopy);

        local w = cc.Director:getInstance():getOpenGLView():getFrameSize().width;
        local h = cc.Director:getInstance():getOpenGLView():getFrameSize().height;

        startForcePressButtonEffect(rescueNodeCopy, w / DESIGN_WIDTH);

        rescueNodeCopy:removeFromParent();

        sender.forceGrade2Handled = true;
    end
end

-- 显示地图图标
function UIMainMap:updateIcon(icon, areaId)
    if DungeonAreaM.isAreaUnlocked(ME.user, areaId) then
        self.areaIcons[areaId]:loadTexture(getAreaNameIconPath(icon));
    end
end

-- 更新地图图标
function UIMainMap:refreshIcons()
    -- 刷新关卡图标
    self:refreshAreas();

    -- 检查佣兵营地
    self:checkPetShop();

    -- 检查马戏团
    self:checkCircus();

    -- 检查邮箱
    self:checkMailBox();

    -- 检查成就
    self:checkAchiv();

    -- 检查飞船
    self:checkAirShip();

    -- 检查遗迹
    self:checkRelic();

    -- 检查活动
    self:checkActivity();

    -- 检查冈布奥觉醒
    self:checkAwake();

    -- 检查命运的链
    self:checkFate();
end

-- 刷新关卡图标
function UIMainMap:refreshAreas()
    local areas = table.keys(DungeonAreaM.getMainAreas());
    local dungeonProgress = ME.user.dbase:query("dungeon_progress", {});
    for i = 1, #areas do
        local areaId = areas[i];
        local icon = DungeonAreaM.query(areaId, "icon");
        if DungeonAreaM.isAreaUnlocked(ME.user, areaId) and DungeonAreaM.isOpenedArea(areaId) then
            self.areaFlags[i]:setVisible(true);
            local flagNode = self.areaFlags[i];
            local tipImg = findChildByName(flagNode, "flag/rescue/tip");

            -- 如果解锁奖励尚未领取
            if not DungeonAreaM.isUnlockBonusTaken(areaId) and
               not DungeonAreaM.isSubDungeonPassed(ME.user, areaId) then
                -- 显示锁头
                tipImg:setVisible(true);
                tipImg:loadTexture("images/ui/main/lock.png");
                playBubbleAnima(tipImg, false, 0.6);
            elseif DungeonAreaM.isParentAreaUnlocked(ME.user, areaId) and not AchievementM.canTakeBonus(areaId) then
                -- 主关卡已解锁，不再显示tip
                tipImg:setVisible(false);
            else
                -- 有子关卡未通关或者有奖励未领取时显示tip
                if not DungeonAreaM.isAllSubAreaPassed(ME.user, areaId) or
                   not DungeonAreaM.isAllSubAreaBonusTaken(ME.user, areaId) or AchievementM.canTakeBonus(areaId) then
                    tipImg:setVisible(false);
                    tipImg:loadTexture("images/ui/main/hint2.png", 1);
                    playBubbleAnima(tipImg, false, 0.6);
                else
                    tipImg:setVisible(false);
                end
            end

            self:refreshTopInfo(flagNode, areaId);
        else
            self.areaFlags[i]:setVisible(false);
        end
    end
end

-- 刷新排行榜显示
function UIMainMap:refreshTopInfo(node, areaId)
    -- 刷新排行榜显示
    local rescueImg    = findChildByName(node, "flag/rescue/rescue_img");
    local toplistImg   = findChildByName(node, "flag/rescue/toplist_img");
    local topNationImg = findChildByName(node, "flag/rescue/toplist_img/nation_img");
    local topInfo      = TopM.getTopData(areaId);
    local mmTopInfo    = TopM.getMainMapTopData(areaId);

    if DungeonAreaM.isAllSubAreaPassed(ME.user, areaId)
       and (#topInfo > 0 or #table.keys(mmTopInfo) > 0) then
        -- 玩家开启该迷宫的无尽模式且有排行榜信息
        -- 设置当前迷宫第一名的国籍信息
        local nationId = TopM.getMainMapTopNation(areaId);
        local icon     = TopM.queryNationality(nationId, "icon");
        topNationImg:loadTexture(getNationalityIconPath(icon));
        rescueImg:setVisible(false);
        toplistImg:setVisible(true);
        topNationImg:setVisible(true);
    else
        -- 否则只显示普通标题
        rescueImg:setVisible(true);
        toplistImg:setVisible(false);
        topNationImg:setVisible(false);
    end
end

-- 检查佣兵营地
function UIMainMap:checkPetShop()
    -- 功能未开启，则隐藏佣兵营地
    local petShop = findChildByName(self.node, "map/pet_shop/pet_shop_icon/icon");
    petShop:loadTexture(getMainIconPath2("slimes"));

    if FeatureItemM.isFeatureOpen("scaffold") == false then
        petShop:setVisible(false);
    else

        petShop:setVisible(true);

        -- 检查提醒
        local hint = findChildByName(petShop, "tip");
        if PetShopM.havePetToExchange() or PetShopM.havePuzzleBox() then
            playBubbleAnima(hint, false, 0.6);
        else
            hint:setVisible(false);
        end
    end
end

-- 检查飞船
function UIMainMap:checkAirShip()
    local airShipImg = findChildByName(self.node, "map/airship/broken_airship");
    local airShipIcon = findChildByName(self.node, "map/airship/airship_icon/icon");
    airShipIcon:loadTexture(getMainIconPath2("airship"));

    -- IOS下，屏蔽掉天空战的入口
    if ServerStatusM.query(MODULE_SWITCH_IOS_BLOCK_CONTENT) == 1 then
        airShipImg:setVisible(true);
        airShipIcon:setVisible(false);
        return;
    end

    if FeatureItemM.isFeatureOpen("key") == false then
        airShipImg:setVisible(true);
        airShipIcon:setVisible(false);
    elseif not NewAirShipM.isFinishGuide() then
        airShipImg:setVisible(true);
        airShipIcon:setVisible(true);

        -- 检查提醒
        local hint = findChildByName(airShipIcon, "tip");
        playBubbleAnima(hint, false, 0.6);
    else
        airShipImg:setVisible(false);
        airShipIcon:setVisible(false);
    end
end

-- 检查冈布奥觉醒
function UIMainMap:checkAwake()
    local awakeIcon = findChildByName(self.node, "map/awake/awake_icon");
    local awake = findChildByName(self.node, "map/awake");

    if FeatureItemM.isFeatureOpen("awake") == false or
        not AwakeM.isAwakeSystemOpen() then
        awakeIcon:setVisible(false);

        awakeIcon:setTouchEnabled(false);
        awake:setTouchEnabled(false);
    else
        awakeIcon:setVisible(true);

        awakeIcon:setTouchEnabled(true);
        awake:setTouchEnabled(true);

        -- 检查提醒
        local hint = findChildByName(self.node, "map/awake/awake_icon/tip");
        playBubbleAnima(hint, false, 0.6);
    end
end

-- 检查遗迹
function UIMainMap:checkRelic()
    local mineNode = findChildByName(self.node, "map/mine");
    local mineEnter = findChildByName(mineNode, "mine_enter/icon");
    mineEnter:loadTexture(getMainIconPath2("relic"));

    local mineIcon = findChildByName(self.node, "map/mine/mine")

    -- IOS下，屏蔽掉天空战的入口
    if ServerStatusM.query(MODULE_SWITCH_IOS_BLOCK_CONTENT) == 1 then
        mineEnter:setVisible(false);
        mineIcon:setVisible(true);
        mineNode:setTouchEnabled(false);
        return;
    end

    if FeatureItemM.isFeatureOpen("key") == false or not NewAirShipM.isFinishGuide() then
        -- 天空之城没开启，引导没完成
        mineEnter:setVisible(false);
        mineIcon:setVisible(true);
        mineNode:setTouchEnabled(false);
        return;
    end

    -- 天空之城开启后即可显示
    mineEnter:setVisible(true);
    mineIcon:setVisible(false);
    mineNode:setTouchEnabled(true);

    -- 检查tip提示
    local needBuildHint = false;
    -- 判断当前有多少掠夺点位/需要有掠夺许可证
    local funcId = SkyRelicFunctionM.getIdsByRule("permit");
    if SkyRelicFunctionM.getLevel(funcId) >= 1 then
        if SkyRelicM.getRelicElementNum() > 0 then
            needBuildHint = true;
        end
    end

    -- 是否有需要复仇的信息
    if #SkyRelicM.getRobRecord() > 0 then
        needBuildHint = true;
    end

    -- 是否有可以升级的项目
    if SkyRelicFunctionM.needShowBubbleForIndex(1)
        or SkyRelicFunctionM.needShowBubbleForIndex(2)
        or SkyRelicFunctionM.needShowBubbleForIndex(3) then
        needBuildHint = true;
    end

    -- 采集满了
    local nowCount = SkyRelicM.getMyRelicInfo("resource")["scrap"] or 0;
    local storeSize = ME.user:queryAttrib("relic_store");
    if nowCount >= storeSize and storeSize > 0 then
        needBuildHint = true;
    end

    -- 检查提醒
    local hint = findChildByName(mineEnter, "tip");
    if needBuildHint then
        playBubbleAnima(hint, false, 0.6);
    else
        hint:setVisible(false);
    end
end

-- 检查马戏团
function UIMainMap:checkCircus()
    -- 功能未开启，则隐藏佣兵营地
    local circus = findChildByName(self.node, "map/circus/circus_icon/icon");
    circus:loadTexture(getMainIconPath2("quest"));
    if FeatureItemM.isFeatureOpen("tick") == false then
        circus:setVisible(false);
    else
        circus:setVisible(true);

        local tipImg = findChildByName(circus, "tip");
        local allReceived = true;
        for _,  taskInfo in ipairs(DailyTaskM.getTaskList()) do
            if taskInfo.received ~= 1 then
                allReceived = false;
                break;
            end
        end

        if not allReceived or DailyTaskM.canGetExtraBonus() or DailyTaskM.canGetCumulationBonus() then
            tipImg:setVisible(true);
            playBubbleAnima(tipImg, false, 0.6);
        else
            tipImg:setVisible(false);
        end
    end
end

-- 检查邮箱
function UIMainMap:checkMailBox()
    -- 功能未开启，则隐藏邮箱
    local mailBox = findChildByName(self.node, "map/mail_box");
    if DungeonAreaM.isDungeonPassed(ME.user, 11) == false then
        -- 尚未通关第一小关，暂不启用邮箱功能
        mailBox:setTouchEnabled(false);
        mailBox:setVisible(false);
    else
        mailBox:setTouchEnabled(true);
        mailBox:setVisible(true);

        local mainBoxIcon = findChildByName(mailBox, "mail_box_icon/icon");
        mainBoxIcon:loadTexture(getMainIconPath2("email"));
        local tipImg = findChildByName(mainBoxIcon, "tip");

        if MailM.getNewMailNum() > 0 then
            tipImg:setVisible(true);
            playBubbleAnima(tipImg, false, 0.6);
        else
            tipImg:setVisible(false);
        end
    end
end

-- 检查成就
function UIMainMap:checkAchiv()
    if self.achivPanel == nil then
        self.achivPanel = findChildByName(self.node, "map/achiv");
    end

    -- 如果成就功能未开启，则隐藏成就节点
    if not GameAchievementM.isFuncOpen() then
        self.achivPanel:setVisible(false);
        return;
    end

    self.achivPanel:setVisible(true);
    local achivImg = findChildByName(self.achivPanel, "achiv_icon/icon");
    achivImg:loadTexture(getMainIconPath2("achiv"));
    local tipImg = findChildByName(achivImg, "tip");

    -- 如果可领取奖励，显示气泡
    if GameAchievementM.canTakeBonus() then
        tipImg:setVisible(true);
        playBubbleAnima(tipImg, false, 0.6);
    else
        tipImg:setVisible(false);
    end
end

-- 检查活动
function UIMainMap:checkActivity()
    if self.activityPanel == nil then
        self.activityPanel = findChildByName(self.node, "map/activity");
    end

    -- 羊群节点
    local sheep2Node = findChildByName(self.node, "map/bg/sheep2");
    local sheep3Node = findChildByName(self.node, "map/bg/sheep3");

    -- 如果活动副本/常规活动均未开启
    -- 必须活动副本通关第一关全部关卡
    local activityId = ActivityDungeonM.getCurrentActivityId()
    if (activityId <= 0 or not ActivityDungeonM.isActivityOpen()) and
        (not RotineActivityM.isInRotineActivity() or not RotineActivityM.isRotineOpen()) then
        self.activityPanel:setVisible(false);
        sheep2Node:setVisible(true);
        sheep3Node:setVisible(true);
        return;
    end

    local bubble = false;

    if RotineActivityM.isInRotineActivity() then
        -- 常规活动
        if RotineActivityM.needToTip() then
            bubble = true;
        end
    else
        -- 活动副本
        -- 如果未全部通关，或者有可以领取的奖励
        if not ActivityDungeonM.isAllPassById(activityId)
            or ActivityDungeonM.hasBonusToTake(activityId) then
            bubble = true;
        end

        -- 对应冈布奥可以兑换
        local activitys = ActivityM.getCurrentActivities();
        for _, id in pairs(table.keys(activitys)) do
            local petId = ActivityDungeonM.query(id, nil, "pet_id");
            if petId and PetShopM.canHirePet(petId) then
                bubble = true;
                break;
            end
        end
    end

    self.activityPanel:setVisible(true);
    sheep2Node:setVisible(false);
    sheep3Node:setVisible(false);
    local activityImg = findChildByName(self.activityPanel, "icon");
    local tipImg = findChildByName(activityImg, "tip");

    -- 提示
    if bubble then
        tipImg:setVisible(true);
        playBubbleAnima(tipImg, false, 0.6);
    else
        tipImg:setVisible(false);
    end
end

-- 播放区域解锁动画
function UIMainMap:playUnlockAreaAnimation(areaId, func)
    self:stopEffect();

    -- 显示mask，吸收玩家此时点击事件
    local mask = self.node:getChildByName("mask");
    mask:setVisible(true);
    mask:setOpacity(255);

    -- 临时调整ZOrder，现在在最前方
    local visible = self:isVisible();
    self:setLocalZOrder(UIMgr.TOP_MENU_ZORDER + 100);
    self:setVisible(true);

    local areas = table.keys(DungeonAreaM.getMainAreas());
    local index = table.indexOf(areas, DungeonAreaM.getParentId(areaId));
    local flagNode = self.areaFlags[index];
        flagNode:setVisible(true);

    local flag = findChildByName(flagNode, "flag");
    local icon = findChildByName(flag, "rescue");
    icon:setVisible(false);

    local new = flag:getChildByName("new");
    new:setVisible(false);

    local effectNode = findChildByName(self.node, "effect_node");
    effectNode:setVisible(true);
    local newDungeonLabel = findChildByName(self.node, "effect_node/new_dungeon");
    local dungeonNameLabel = findChildByName(self.node, "effect_node/dungeon_name");
    newDungeonLabel:setPositionX(0);
    newDungeonLabel:removeAllChildren();
    dungeonNameLabel:setPositionX(0);
    dungeonNameLabel:setString("");
    dungeonNameLabel:removeAllChildren();

    local titleStr = getLocStr("new_dungeon_title");
    local parentId = DungeonAreaM.getParentId(areaId);
    local nameStr = DungeonAreaM.query(parentId, "name");
    local titleFontSize = 73 * getLocFontSizeMulti() * TextStyleM.getTextScale(titleStr, 12);
    local nameFontSize = 53 * getLocFontSizeMulti();
    local maskBgPath = "images/ui/text_mask/text_mask4.png";
    local maskBgPath2 = "images/ui/text_mask/text_mask2.png";

    local function dungeonNameFlyOut()
        -- 副本名飞出
        local moveOut = cc.MoveTo:create(0.2, cc.p(DESIGN_WIDTH * 1.5, newDungeonLabel:getPositionY()));
        local moveOut2 = cc.MoveTo:create(0.2, cc.p(-DESIGN_WIDTH * 2, dungeonNameLabel:getPositionY()));
        newDungeonLabel:stopAllActions();
        dungeonNameLabel:stopAllActions();
        newDungeonLabel:runAction(moveOut);
        dungeonNameLabel:runAction(moveOut2);

        local function next()
            -- 背景渐隐
            mask:runAction(cc.FadeOut:create(0.5));

            icon:setVisible(true);
            icon:setOpacity(0);

            local fadeIn = cc.FadeIn:create(0.3);
            local scaleUp = cc.ScaleTo:create(0.2, 1.1);
            local scaleDown = cc.ScaleTo:create(0.1, 1.0);
            local callFunc = cc.CallFunc:create(function()
                commonTextEffect(icon);
            end);

            local delay = cc.DelayTime:create(0.5);

            local callFunc2 = cc.CallFunc:create(function()
                new:setVisible(true);
                self:setLocalZOrder(self.oldZOrder);
                self:setVisible(visible);
                mask:setVisible(false);
                self:resumeEffect();

                if func then
                    func();
                end
            end);

            icon:runAction(cc.Sequence:create(fadeIn, callFunc, delay, callFunc2));
        end

        performWithDelay(mask, next, 0.5);
    end

    -- 副本名飞入
    local function dungeonNameFlyIn()
        playStampEffect(dungeonNameLabel, nameStr, nameFontSize, dungeonNameFlyOut, maskBgPath2, { self });
    end

    -- "新关卡出现"飞入
    playFlyInEffect(newDungeonLabel, titleStr, titleFontSize, dungeonNameFlyIn, maskBgPath);

    -- 滚动到中心区域
    local map = findChildByName(self.node, "map");
    local size = map:getInnerContainerSize();

    local percent = (flagNode:getPositionY() - 600) * 100 / size.height;
    if percent < 0 then
        percent = 0;
    end

    map:jumpToPercentVertical(percent);
end

-- 检查暂离状态
function UIMainMap:checkPauseState()
    self:checkPetShop();
    self:checkCircus();
    self:checkMailBox();
    self:checkAchiv();

    local blendColor = TextStyleM.TEXT_COLOR_BLEND;
    local normalColor = TextStyleM.TEXT_COLOR_NORMAL;

    local areaId =  ME.user.dbase:query("current_dungeon");

    local map = findChildByName(self.node, "map");

    -- 相关节点颜色
    -- SLIMEC-3367 暂离状态颜色正常
    if DungeonM.isInDungeon() then
        -- 置灰
        for _, node in pairs(blendNodes) do
            node:setColor(blendColor);
        end
     else
        -- 高亮
        for _, node in pairs(blendNodes) do
            node:setColor(normalColor);
        end
    end

    local children = map:getChildren();
    local len = table.getn(children)
    for i = 1, len do
        local child = children[i];
        local flag = findChildByName(child, "flag");
        if flag ~= nil then
            local tag = flag:getTag();

            -- SLIMEC-3367 暂离状态颜色正常
            -- if not DungeonAreaM.isInSameArea(tag, areaId) and DungeonM.isInDungeon() then
            --     flag:setColor(blendColor);
            -- else
                flag:setColor(normalColor);
            -- end
        end
    end

    self:checkLimitGiftBonus();
end

-- 播放特效
function UIMainMap:playEffect()
    if self.effectsPlaying then
        return;
    end

    self.effectsPlaying = true;

    for _, node in pairs(self.effectNodes or {}) do
        node:removeAllChildren();
    end

    self.effectNodes = {};

    local function onImageLoaded()

        -- 海盗港湾
        local piratesHarbourNode1 = findChildByName(self.node, "map/bg/port1");
        local piratesHarbourNode2 = findChildByName(self.node, "map/bg/port1");
        playPiratesHarbourEffect1(piratesHarbourNode1);
        playPiratesHarbourEffect2(piratesHarbourNode2);
        table.insert(self.effectNodes, piratesHarbourNode1);
        table.insert(self.effectNodes, piratesHarbourNode2);

        -- 骷髅岛
        local skullIslandNode = findChildByName(self.node, "map/bg/skull_island");
        playSkullIslandEffect(skullIslandNode);
        table.insert(self.effectNodes, skullIslandNode);

        -- 骷髅岛旁小岛
        local islandNode = findChildByName(self.node, "map/bg/island");
        playIslandEffect(islandNode);
        table.insert(self.effectNodes, islandNode);

        -- 海浪
        local wavesNode = findChildByName(self.node, "map/bg/waves");
        playWavesEffect(wavesNode);
        table.insert(self.effectNodes, wavesNode);

        -- 世界树
        local worldTreeNode = findChildByName(self.node, "map/bg/world_tree");
        playWorldTreeEffect(worldTreeNode);
        table.insert(self.effectNodes, worldTreeNode);

        -- 炼金坊
        local blacksmithNode1 = findChildByName(self.node, "map/bg/blacksmith1");
        local blacksmithNode2 = findChildByName(self.node, "map/bg/blacksmith2");
        local blacksmithNode3 = findChildByName(self.node, "map/bg/blacksmith3");
        playBlacksmithEffect1(blacksmithNode1);
        playBlacksmithEffect2(blacksmithNode2);
        playBlacksmithEffect3(blacksmithNode3);
        table.insert(self.effectNodes, blacksmithNode1);
        table.insert(self.effectNodes, blacksmithNode2);
        table.insert(self.effectNodes, blacksmithNode3);

        -- 羊
        local sheep2Node = findChildByName(self.node, "map/bg/sheep2");
        local sheep3Node = findChildByName(self.node, "map/bg/sheep3");
        playSheepEffect2(sheep2Node);
        playSheepEffect1(sheep3Node);
        table.insert(self.effectNodes, sheep2Node);
        table.insert(self.effectNodes, sheep3Node);

        -- 旗子
        local flagNode = findChildByName(self.node, "map/bg/flag");
        playFlagEffect(flagNode);
        table.insert(self.effectNodes, flagNode);

        -- 低语之森蒸汽
        local whisperForest2Node = findChildByName(self.node, "map/bg/whisper_forest1");
        playWhisperForestEffect2(whisperForest2Node);
        table.insert(self.effectNodes, whisperForest2Node);

        -- 低语之森彩虹
        local whisperForest3Node = findChildByName(self.node, "map/bg/whisper_forest2");
        playWhisperForestEffect3(whisperForest3Node);
        table.insert(self.effectNodes, whisperForest3Node);

        -- 佣兵营地
        local campNode1 = findChildByName(self.node, "map/bg/camp1");
        local campNode2 = findChildByName(self.node, "map/bg/camp2");
        playCampEffect1(campNode1);
        playCampEffect2(campNode2);
        table.insert(self.effectNodes, campNode1);
        table.insert(self.effectNodes, campNode2);

        -- 圣者之塔符石
        local sageNode = findChildByName(self.node, "map/bg2/sage_tower");
        playSageTowerEffect(sageNode);
        table.insert(self.effectNodes, sageNode);

        -- 熔岩泡泡
        local lavaNode = findChildByName(self.node, "map/bg2/lava");
        playLavaEffect(lavaNode);
        table.insert(self.effectNodes, lavaNode);

        -- 混沌深渊
        local abyssNode = findChildByName(self.node, "map/bg2/abyss");
        playAbyssEffect(abyssNode);
        table.insert(self.effectNodes, abyssNode);

        -- 蝙蝠飞出
        local batNode = findChildByName(self.node, "map/bg2/bat");
        playBatEffect(batNode);
        table.insert(self.effectNodes, batNode);

        -- 传送门
        local portalNode = findChildByName(self.node, "map/bg2/portal");
        playPortalEffect(portalNode);
        table.insert(self.effectNodes, portalNode);

        -- 雪
        local snowNode = findChildByName(self.node, "map/bg2/snow");
        playSnowEffect(snowNode);
        table.insert(self.effectNodes, snowNode);

        -- 蒸汽之城
        local steamCityNode = findChildByName(self.node, "map/bg2/steam_city");
        playSteamEffect(steamCityNode);
        table.insert(self.effectNodes, steamCityNode);

        self.isEffectPlaying = true;

        -- 检查特效
        self:checkEffects();
    end

    local function loadedEffects2()
        cc.Director:getInstance():getTextureCache():addImageAsync("images/effect_anim/map_effects2.png", onImageLoaded);
    end
    cc.Director:getInstance():getTextureCache():addImageAsync("images/effect_anim/map_effects.png", loadedEffects2);
end

-- 检查特效是否在可视区域内
function UIMainMap:checkEffects()
    local function checkMainMapEffects()
        if self:isVisible() then
            for i = 1, #self.effectNodes do
                local node = self.effectNodes[i];
                if not isEffectNodeVisible(node) then
                    -- 特效节点不可见，暂停播放
                    if not node.isPause then
                        pauseNode(node);
                        node.isPause = true;
                    end
                else
                    -- 可见，继续播放
                    if node.isPause then
                        resumeNode(node);
                        node.isPause = false;
                    end
                end
            end

            -- 彩蛋检查
            self.eggs:checkEffect();
        end
        performWithDelay(self, checkMainMapEffects, 2);
    end

    performWithDelay(self, checkMainMapEffects, 2);
end

-- 停止特效
function UIMainMap:stopEffect()
    self.isEffectPlaying = false;

    -- 主地图特效
    if not self.effectNodes then
        return;
    end

    for i = 1, #self.effectNodes do
        pauseNode(self.effectNodes[i]);
    end

    -- 彩蛋
    self.eggs:stopEffect();
end

-- 继续特效
function UIMainMap:resumeEffect()
    self.isEffectPlaying = true;

    if not self.effectNodes then
        return;
    end

    for i = 1, #self.effectNodes do
        resumeNode(self.effectNodes[i]);
    end

    self.eggs:resumeEffect();
end

-- 释放特效
function UIMainMap:releaseEffect()
    if self.effectNodes == nil then
        return;
    end

    for i = 1, #self.effectNodes do
        self.effectNodes[i]:stopAllActions();
        self.effectNodes[i]:removeAllChildren();
    end

    self.effectNodes = {};
end

-- 隐藏
function UIMainMap:hide()
    self:setVisible(false);

    UIMgr.getCurrentScene().bottomMenu:setVisible(false);

    -- 停止特效
    self:stopEffect();
end

-- 显示
function UIMainMap:show()
    self:setVisible(true);

    -- 新添BOTTOM飞入效果，所以这段不需要
    --- UIMgr.getCurrentScene().bottomMenu:setVisible(true);

    -- 继续特效
    self:resumeEffect();
end

-- 播放冈布奥从炼金工坊跑出来的动画
function UIMainMap:playSlimeAnimation()
    -- 停止特效
    for i = 1, #self.effectNodes do
        self.effectNodes[i]:pause();
    end

    -- 隐藏菜单
    UIMgr.getCurrentScene().topMenu:setVisible(false);
    UIMgr.getCurrentScene().bottomMenu:setVisible(false);

    -- 隐藏第一关
    local flagNode = self.areaFlags[1];
    flagNode:setVisible(false);

    local slime = findChildByName(self.node, "map/bg/slime");
    slime:setVisible(true);
    slime:loadTexture("images/hero/1.png");
    slime:setScaleX(-slime:getScaleX());

    local bubble = findChildByName(slime, "bubble");

    -- 跑到世界树，左右跳几下
    local jumpTo = cc.JumpTo:create(1, cc.p(256, 418), 30, 4);
    local delay = cc.DelayTime:create(1.0);
    local jumpTo2 = cc.JumpTo:create(0.3, cc.p(316, 418), 30, 1);
    local callFunc2 = cc.CallFunc:create(function()
        slime:setScaleX(-slime:getScaleX());
    end);

    local jumpTo3 = cc.JumpTo:create(0.3, cc.p(256, 418), 30, 1);
    local callFunc3 = cc.CallFunc:create(function()
        slime:setScaleX(-slime:getScaleX());
    end);
    local jumpAround = cc.Sequence:create(jumpTo2, callFunc2, jumpTo3, callFunc3);

    local delay2 = cc.DelayTime:create(0.5);
    local happy = cc.CallFunc:create(function()
        -- 头上冒心
        -- bubble:setVisible(true);
    end);

    local delay3 = cc.DelayTime:create(0.5);
    local openArea = cc.CallFunc:create(function()
        local function callback()
            bubble:setVisible(false);
            UIMgr.getCurrentScene().topMenu:setVisible(true);
            UIMgr.getCurrentScene().bottomMenu:setVisible(true);

            -- 冈布奥蹦跳这走进森林，开始冒险之旅
            local jumpTo4 = cc.JumpTo:create(1, cc.p(194, 394), 30, 4);
            local callFunc4 = cc.CallFunc:create(function()
                slime:setVisible(false);

                -- 打开区域界面
                local uiEnter = UISubAreaList.create(1);
                UIMgr:getCurrentScene():addForm(uiEnter);
            end);
            slime:runAction(cc.Sequence:create(jumpTo4, callFunc4));
        end

        -- 激活新关卡提示
        slime:setScaleX(-slime:getScaleX());
        self:playUnlockAreaAnimation(1, callback);

        -- 抛出第一个关卡解锁事件
        EventMgr.fire(event.UNLOCK_FIRST_AREA);
    end);

        -- 跳动到树下音效
        performWithDelay(self, function() AudioM.playFx("slime_jump"); end, 0.0);
        performWithDelay(self, function() AudioM.playFx("slime_jump"); end, 0.5);
        performWithDelay(self, function() AudioM.playFx("slime_jump1"); end, 0.9);
        performWithDelay(self, function() AudioM.playFx("slime_jump1"); end, 2.0);
        performWithDelay(self, function() AudioM.playFx("slime_jump2"); end, 2.4);

        performWithDelay(self, function() AudioM.playFx("slime_jump2"); end, 8.1);
        performWithDelay(self, function() AudioM.playFx("slime_jump2"); end, 8.5);
    slime:runAction(cc.Sequence:create(jumpTo, delay, jumpAround, delay2, happy, delay3, openArea));
end

-- 引导进入地牢
function UIMainMap:guideToEnterDungeon()
    -- 注册窗口回调
    EventMgr.removeAll("UIMainMapGuide");
    EventMgr.register("UIMainMapGuide", event.ADD_FORM, function(params)
        local name = params["name"];
        print("addFrom:"..name);
        if name == "UISubAreaList" then
            local function callback()
                local form = UIMgr.getCurrentScene():getFormByName(name);
                if form ~= nil then
                    local uiGuide = UIGuideMask.create(form, "TOP/scrollview/item1/bg");
                    UIMgr.getCurrentScene():addForm(uiGuide);
                end
            end
            wait(0.5, callback);
        elseif name == "UIEnterDungeon" then
            local function callback()
                local form = UIMgr.getCurrentScene():getFormByName(name);
                if form ~= nil then
                    local uiGuide = UIGuideMask.create(form, "CT/enter_btn");
                    UIMgr.getCurrentScene():addForm(uiGuide);
                    EventMgr.remove("UIMainMapGuide", event.ADD_FORM);
                end
            end
            wait(0.5, callback)
        end
    end);
end

-- 顶部和底部菜单飞出屏幕
function UIMainMap:topAndBottomMenuFlyOut()
    local topMenu = UIMgr.getCurrentScene().topMenu;
    if topMenu then
        topMenu:flyOut();
    end

    local bottomMenu = UIMgr.getCurrentScene().bottomMenu;
    if bottomMenu then
        bottomMenu:flyOut();
    end
end

-- 顶部和底部菜单飞入屏幕
function UIMainMap:topAndBottomMenuFlyIn()
    -- 清除无用纹理
    -- UIMgr.clearUnusedTextures();

    local topMenu = UIMgr.getCurrentScene().topMenu;
    if topMenu then
        topMenu:show(TOPMENU_FRAME_TREE, TOPMENU_FRAME_LIFE);
    end

    local bottomMenu = UIMgr.getCurrentScene().bottomMenu;
    if bottomMenu then
        bottomMenu:setVisible(true);
        bottomMenu:updateState();
        bottomMenu:flyIn();
    end

    -- 检查是否需要提醒
    self:checkReminder();
end

-- 依次检查是否有提醒
function UIMainMap:checkReminder()
    local checkList = { "scaffold", "tick", "key", "machine", "awake"};

    local function callback(desc)
        local function okFunc()
            -- 继续检查是否还有其他提醒
            self:checkReminder();
        end

        confirm2(CONFIRM_TYPE_OK, getLocStr("slime_note"), desc, okFunc);
    end

    -- 检查进度
    if not self.checkProgress then
        self.checkProgress = 1;
    end

    print("self.checkProgress:"..self.checkProgress);
    for i = self.checkProgress, #checkList do
        local ret = FeatureItemM.checkReminder(checkList[i], callback);
        if ret == true then
            -- 每次检查一个提醒，确认后再继续
            self.checkProgress = i + 1;
            return;
        end
    end
end

-- 获取炼金剩余时间
function UIMainMap:getWorkShopTime()
    return self.workShopLeftTime;
end

resize = function(node)
    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;

    local map = node:getChildByName("map");
    map:setPosition(0, 0);
    local scrollViewSize = map:getContentSize();
    map:setContentSize(scrollViewSize.width, scrollViewSize.width * h / w);
    map:setBounceBoundaryY(150);
    map:setScale(w / DESIGN_WIDTH);

    local mask = node:getChildByName("mask");
    mask:setContentSize(w + 100, h + 100);
    mask:setPosition(w / 2, h / 2);

    local effectNode = node:getChildByName("effect_node");
    effectNode:setScale(h / DESIGN_HEIGHT);
    effectNode:setPosition(w / 2, 0);
end

-- 处理返回键输入
function UIMainMap:registerBackKeyEventHandler()
    local mask = self.node:getChildByName("map");

    local function onKeyReleased(keyCode, event)
        if keyCode == cc.KeyCode.KEY_BACK then
            -- print("BACK clicked!")
            UIMgr.handleBackEvent();
        end
    end

    local listener = cc.EventListenerKeyboard:create()
    listener:registerScriptHandler(onKeyReleased, cc.Handler.EVENT_KEYBOARD_RELEASED )

    local eventDispatcher = mask:getEventDispatcher()
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener, mask);
end

-- 移除界面
function UIMainMap:removeForms()
    -- 移除除了"UIMainMap", "UITopMenu", "UIBottomMenu"之外的所有界面， UIDungeonMain也包括进来，后面自己会删除
    UIMgr:getCurrentScene():removeAllFormWithoutList({"UIMainMap", "UITopMenu", "UIBottomMenu", "UIDungeonMain"});
end

-- 登录后检查在迷宫中
function UIMainMap:checkInDungeonState()
    self:checkPetShop();

    local blendColor = TextStyleM.TEXT_COLOR_BLEND;
    local normalColor = TextStyleM.TEXT_COLOR_NORMAL;

    local dungeonId = ME.user.dbase:query("current_dungeon");

    local map = findChildByName(self.node, "map");

    -- 相关节点颜色
    if DungeonM.isInDungeon() then
        -- 置灰
        for _, node in pairs(blendNodes) do
            node:setColor(blendColor);
        end
    else
        -- 高亮
        for _, node in pairs(blendNodes) do
            node:setColor(normalColor);
        end
    end

    local children = map:getChildren();
    local len = table.getn(children)
    for i = 1, len do
        local child = children[i];
        local flag = findChildByName(child, "flag");
        if flag ~= nil then
            local tag = flag:getTag();
            if DungeonM.isInDungeon() and not DungeonAreaM.isInSameArea(tag, dungeonId) then
                flag:setColor(blendColor);
            else
                flag:setColor(normalColor);
            end
        end
    end

    self:checkLimitGiftBonus();
end

-- 还原地图颜色
function UIMainMap:setNormalColor()
    self:checkPetShop();

    local normalColor = TextStyleM.TEXT_COLOR_NORMAL;

    local dungeonId = ME.user.dbase:query("current_dungeon");

    local map = findChildByName(self.node, "map");

    -- 相关节点颜色还原
    for _, node in pairs(blendNodes) do
        node:setColor(normalColor);
    end

    local children = map:getChildren();
    local len = table.getn(children)
    for i = 1, len do
        local child = children[i];
        local flag = findChildByName(child, "flag");
        if flag ~= nil then
            flag:setColor(normalColor);
        end
    end

    self:checkLimitGiftBonus(true);
end

-- 显示dp信息
function showDpInfo(areaId)
    local uiDpExplain = UIDpExplain.create(areaId);
    UIMgr.getCurrentScene():removeFormByName("UIDpExplain");
    UIMgr.getCurrentScene():addForm(uiDpExplain);
end

-- 该列表记录进入地牢后可释放的资源列表。不可随便添加或删除！
local mainMapResList = {
    "images/ui/main/top.jpg",
    "images/ui/common/map_bg2.jpg",
    "images/ui/common/map_bg.jpg",
    "images/ui/main/bottom.jpg",
    "images/effect_anim/map_effects.png",
    "images/effect_anim/map_effects2.png",
};

-- 释放主地图图片资源
-- 节点保持不变，释放占用内存大的图片
function UIMainMap:releaseImages()
    local bg = findChildByName(self.node, "map/bg");
    local bg2 = findChildByName(self.node, "map/bg2");
    local bottom = findChildByName(self.node, "map/bottom");
    local top = findChildByName(self.node, "map/top");

    -- 释放地图底图资源
    bg:loadTexture("Default/ImageFile.png");
    bg2:loadTexture("Default/ImageFile.png");
    bottom:loadTexture("Default/ImageFile.png");
    top:loadTexture("Default/ImageFile.png");

    bg:setVisible(false);
    bg2:setVisible(false);
    bottom:setVisible(false);
    top:setVisible(false);

    -- 释放特效资源
    self:releaseEffect();

    -- 释放彩蛋资源
    self.eggs:releaseEffect();

    for _, path in pairs(mainMapResList) do
        cc.Director:getInstance():getTextureCache():removeTextureForKey(path);
    end
end

-- 提示处理
local function checkHints()
    -- 5. 获取炼金奖励
    local function check5()
        local uiForm = UIMgr:getCurrentScene():getFormByName("UIDungeonMain");
        if uiForm then
            -- 迷宫界面开着，认为在迷宫中
            return;
        end

        local text, count = AlchemyWorkshopM.fetchBonusText();
        if text ~= "" then
            local function delayShow()
                local confirmForm;
                if count < 2 then
                    confirmForm = confirm2(CONFIRM_TYPE_OK, getLocStr("workshop_bonus_tip_title"), text);
                else
                    confirmForm = confirm4(CONFIRM_TYPE_OK, getLocStr("workshop_bonus_tip_title"), text);
                end
            end
            performWithDelay(UIMgr.getCurrentScene(), delayShow, 0.5);
        end
    end

    -- 4. 如果有升级提示信息，则弹出
    local function check4()
        ME.user.dbase:deleteTemp("isDungeonPassed");

        local msg = ME.user.dbase:queryTemp("level_up_hint");
        if type(msg) == 'string' then
            showHint(msg, false, check5);
            ME.user.dbase:deleteTemp("level_up_hint");
        else
            check5();
        end
    end

    -- 2. 如果有新区域解锁， 播放动画
    local function check2()
        local catch = ME.user.dbase:queryTemp("isDungeonPassed", 0);
        if type(catch) == "table" and not catch.pass and DungeonAreaM.isDungeonPassed(ME.user, catch.id) then
            local newUnlockAreaId = ME.user.dbase:queryTemp("new_unlock_area");
            if newUnlockAreaId ~= nil then
                UIMgr:getCurrentScene().mainMap:playUnlockAreaAnimation(newUnlockAreaId, check4);
                ME.user.dbase:deleteTemp("new_unlock_area");
            else
                check4();
            end
        else
            check4();
        end
    end

    check2();
end

-- 重新加载主地图资源
function UIMainMap:reloadImages()
    self:setVisible(false);

    local function doReload()
        -- 为防止UICommunicating界面可能被其他操作关闭掉，再增加一层黑色蒙版，以防万一
        showBlackForm("UIReloadImages");

        -- 异步加载，加载完成后重新初始化主地图相关界面，同时显示"加载中"
        if DungeonM.isPause() then
            showCommunicatingForm(getLocStr("loading"), true, true);
        else
            showCommunicatingForm(getLocStr("loading"), true);
        end
        loadImagesAsync(mainMapResList, function()
            print("主地图资源重新加载完毕")

            self:setVisible(true);

            -- 关闭"加载中"提示
            local delay = 0;
            if UIMgr:getCurrentScene():isOpen("UICommunicating") then
                delay = closeCommunicatingForm();
            end

            -- 关闭黑色蒙版
            hideBlackForm("UIReloadImages");

            -- 重新加载地图底图
            local bg = findChildByName(self.node, "map/bg");
            local bg2 = findChildByName(self.node, "map/bg2");
            local bottom = findChildByName(self.node, "map/bottom");
            local top = findChildByName(self.node, "map/top");

            bg:setVisible(true);
            bg2:setVisible(true);
            bottom:setVisible(true);
            top:setVisible(true);

            bg:loadTexture("images/ui/common/map_bg.jpg");
            bg2:loadTexture("images/ui/common/map_bg2.jpg");
            bottom:loadTexture("images/ui/main/bottom.jpg");
            top:loadTexture("images/ui/main/top.jpg");

            -- 重新加载特效
            self.effectsPlaying = false;
            self:playEffect();

            -- 重新显示彩蛋
            self.eggs:displayEggs();

            -- 检查提示信息

            -- 添加暂离提示
            if DungeonM.isPause() then
                local function reminder()
                    MsgReminderM.addReminder(MsgReminderM.MSG_REMINDER_TYPE_PAUSE, {["id"] = 1});
                end

                performWithDelay(UIMgr.getCurrentScene(), reminder, 0.1);
                performWithDelay(UIMgr.getCurrentScene(), checkHints, math.max(delay, 0.4));
            else
                performWithDelay(UIMgr.getCurrentScene(), checkHints, math.max(delay, 0.4));
            end

        end);
    end
    performWithDelay(UIMgr.getCurrentScene(), doReload, 0.01);
end

-- 初始化需要暂离置灰的节点
function UIMainMap:initBlendNodes()
    local map = findChildByName(self.node, "map");
    local bg = findChildByName(map, "bg");
    local bg2 = findChildByName(map, "bg2");
    local eggs = findChildByName(map, "egg_parent_node");
    local bottom = findChildByName(map, "bottom");
    local top = findChildByName(map, "top");
    local airShipImg1 = findChildByName(self.node, "map/airship/broken_airship");
    local mineIcon = findChildByName(self.node, "map/mine/mine");
    local activityImg = findChildByName(self.node, "map/activity/activity_icon");
    local activityEffect = findChildByName(self.node, "map/activity/effect");

    blendNodes = {bg, bg2, eggs, bottom, top, airShipImg1, mineIcon, activityImg, activityEffect};
end

-- 当前活动副本关注的冈布奥碎片
function UIMainMap:forcusActivityScraps()
    -- 当前活动本
    local activitys = ActivityM.getCurrentActivities();
    if #activitys <= 0 then
        -- 当前没有活动
        return {};
    end

    -- 碎片
    local ret = {};
    for _, id in pairs(table.keys(activitys)) do
        local petId = ActivityDungeonM.query(id, nil, "pet_id");
        if petId then
            table.insert(ret, PetM.query(petId, "chips"));
        end
    end

    return ret;
end

-- 检查命运的链
function UIMainMap:checkFate()
    if self.fatePanel == nil then
        self.fatePanel = findChildByName(self.node, "map/fate");
    end
    local fateIcon = findChildByName(self.node, "map/fate/fate_icon/icon");
    fateIcon:loadTexture(getMainIconPath2("stelae"));
    fateIcon:setTouchEnabled(true);

    -- 判断能否显示
    if not FateChainM.canShowEnter() then
        self.fatePanel:setVisible(false);
        return;
    end

    self.fatePanel:setVisible(true);
    local tipImg = findChildByName(self.fatePanel, "fate_icon/icon/tip");

    -- 判断是否需要提示
    if FateChainM.needTip() then
        tipImg:setVisible(true);
        playBubbleAnima(tipImg, false, 0.6);
    else
        tipImg:setVisible(false);
    end
end

return UIMainMap;
